Skip to content

Merge upstream - #201

Merged
KotlinIsland merged 372 commits into
mainfrom
merge-upstream
Sep 6, 2026
Merged

KotlinIsland merged 372 commits into
mainfrom
merge-upstream

Conversation

@KotlinIsland

Copy link
Copy Markdown
Owner

No description provided.

zaniebot and others added 30 commits August 19, 2026 10:12
Pinning `cloudflare/wrangler-action` does not pin the `wrangler@4`
package it installs during deployment. Install Wrangler from a
dedicated, committed lockfile with lifecycle scripts disabled, then
invoke that local executable directly. Require `npm>=11.10.0` with
`engines.npm` and `engine-strict` so older clients cannot silently
ignore the seven-day `min-release-age` policy, and pin Node.js to
`24.19.0` at the install sites. Both playgrounds now use the reviewed
dependency tree already recorded for the playground API, and Cloudflare
credentials are passed only to the deployment command.

Related: astral-sh/ruff#27839 isolates the playground builds from
deployment credentials, and astral-sh/ruff#27844 enforces the
corresponding `npm` floor in the existing package roots.
astral-sh/ruff-vscode#1152 and astral-sh/ty-vscode#526 likewise give
credentialed publishers their own locked toolchains.

---------

Co-authored-by: zaniebot <[email protected]>
Fixes astral-sh/ty#4308 by separating two questions: does a parameter
have a default, and what type does that default expression have?

## The failure

This small example currently causes ty to panic with `too many cycle
iterations`:

```python
f = lambda: f
assert f

@Property
def f(x=lambda: f): ...
```

To determine what arguments the decorated function accepts, ty currently
infers the types of all its defaults. Here, looking into `lambda: f`
leads back to the decorated `f`, through the earlier assertion, and into
the same inference work again. The repeated attempts never settle.

The fix is to let ty determine that `x` has a default value (can be
omitted from calls) without first working out the type of `lambda: f`.

## What changes

Consider an ordinary function and a caller in another file:

```python
# defaults.py
def f(x: int = 1) -> int:
    return x
```

```python
# main.py
from defaults import f

result = f()
```

Checking the call needs to know that `x` accepts an `int` and can be
omitted. It does not need the particular default value. Changing `1` to
`2` therefore does not require rechecking this caller. Removing the
default does: `f()` becomes an error.

Default expressions are still checked when checking the file that
defines the function. Code that genuinely needs the default's type can
request it separately. For example, changing a custom dataclass field
helper's `init` default from `False` to `True` must still update the
generated constructor.

## Implementation

Source parameters keep a reference to their definition instead of
storing an already-inferred default type. `has_default()` answers
whether an argument may be omitted; `default_type(db)` resolves the type
when needed.

Annotations remain in the existing `infer_deferred_types` cache. A
separate `infer_function_default_types` cache contains only defaults,
and normal file checking combines the two results. This avoids storing
annotation data twice. Functions with only annotations or only defaults
also avoid creating an empty second result. Overall this reduces memory
usage relative to main.

One awkward point in the implementation is that prior to this PR, a
literal promotion type mapping also promotes the literal types of
default values. Preserving this behavior would require an additional
`DeferredPromoted` default-value enum type, to track the need for
promotion of deferred defaults. But IMO it does not make sense that
literal-promoting a callable type would literal-promote the types of its
default values, which strictly speaking aren't even part of the callable
type. So I just removed that entirely. This shows up in the ecosystem as
some signatures now showing actual defaults rather than `...`, because
we preserve literal default values when displaying types but transform
nominal types to `...`. I think we probably need a more coherent policy
around always erasing specific default types from general callable
types, but that's too far afield for this PR.

## Test plan

- Cycle mdtests cover the reported example, annotated variants, direct
`property(...)` construction, a custom decorator, a ParamSpec decorator
that changes the call signature, and a generic property getter.
- A decorator mdtest checks that reporting an unknown decorator return
handles a self-referential default and still identifies the function's
signature.
- Incremental tests check that ordinary calls do not infer defaults.
Changing only a default value leaves those callers alone; removing and
restoring the default updates missing-argument errors. Concrete
signature display reflects the new value.
- A dataclass regression changes a field helper's `init` default from
`False` to `True` and back, checking the generated constructor's call
diagnostics.
- Cache tests verify that annotation and default results remain
separate, and that functions needing only one do not create the other.
- A promotion mdtest checks that a `partial` retains both its source
default and a keyword-bound default when placed in a collection.

### Ecosystem

Some duplicate diagnostics go away because main double-inferred a
function's decorators when deferred annotations or defaults were
inferred; this PR fixes that.

Other changes are the default-values promotion change mentioned above.
CodSpeed's dependency installation can stall on an unhealthy apt mirror.
Apply the mitigation from astral-sh/uv#21206 so
apt gives up on failed downloads sooner and can reach its configured
fallback mirrors.

Immediately before CodSpeed runs, disable apt download retries and set
ten-second HTTP and HTTPS timeouts. Apply this to the Ruff instrumented
job and the simulation entries in the ty instrumented matrix. Leave
build-only, memory-only, and walltime jobs unchanged.

This does not change CodSpeed's cache detection. If every configured
mirror is unavailable, installation may fail sooner rather than succeed.

## Test plan

No new mdtests are needed for this workflow-only change. Validate the
workflow's formatting, schema, security checks, and shell syntax. CI
exercises the Ruff instrumented job and ty simulation matrix with the
new apt configuration.
## Summary

When an `invalid-assignment` diagnostic is caused by a previously
declared type, point directly to the annotation that established that
type instead of only annotating the assignment target.

## Example

```py
value: int
value = "three"
```

```text
error[invalid-assignment]: Object of type `Literal["three"]` is not assignable to `int`
 --> example.py:2:9
  |
1 | value: int
  |        --- Declared type
2 | value = "three"
  |         ^^^^^^^ Incompatible value of type `Literal["three"]`
```
Bounded intersection expansion could silently drop alternatives after
the first four members of a union. The first union is intentionally
exempt from the expansion budget, but ignoring a failed insertion did
not actually insert the omitted member. This let the helper return a
partial type as though it were exact, causing false-positive diagnostics
such as the regression exposed by #27664.

Apply the budget exemption inside candidate insertion. Preserve every
alternative of the first union, while continuing to return `None` if
later expansion exceeds the budget.

Fixes astral-sh/ty#4335.

## Test plan

- Unit tests cover late union members surviving an intersection in
either operand order, the single-union fast path, and genuine budget
exhaustion returning `None` instead of a partial result.
- Mdtests cover redundant upper bounds on five-member gradual unions,
including a reduced recursive-alias regression with `Divergent` members.
(Stacked on top of #27872)

## Summary

Add a disabled-by-default `unsound-assignment` rule for variable
assignments whose inferred value is assignable to a fully static
declared type but is not a subtype of it. Attribute and subscript
assignments are deliberately excluded from this initial implementation.

Share declaration lookup and diagnostic rendering with
`invalid-assignment` so the rule points to the annotation responsible
for the expected type, including parameters, variadics, global/nonlocal
bindings, and augmented assignments.

Fixes astral-sh/ty#4289.
## Summary

Specializing a non-generic class in a type expression currently emits
`not-subscriptable`, even when the class defines `__class_getitem__` and
subscription works at runtime. Report `invalid-type-form` instead,
explaining that the class cannot be specialized in a type expression and
linking to the typing specification.

Apply the correction both to direct annotations and to specializations
nested inside `type[...]`. Keep `Unknown` recovery and runtime
subscription behavior unchanged.

Existing suppressions of `not-subscriptable` no longer suppress these
type-form errors; projects that intentionally ignore them can suppress
`invalid-type-form` instead.

Fixes astral-sh/ty#4331.

## Test plan

Update the existing non-generic-class expectations. Add mdtests covering
custom `__class_getitem__` in value expressions, direct and nested type
annotations, future annotations, Python 3.14's deferred annotations, and
the full diagnostic output.
`scripts/release.sh` uses `--no-config` to avoid recording Socket's
registry URLs, but this also discards `exclude-newer` and other
configuration. Set `--default-index` to PyPI for the lockfile update and
crates.io publishing check so these commands use the public default
index while retaining the remaining settings.

Co-authored-by: zaniebot <[email protected]>
…erenced by `from ...` imports (#27905)

<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
- Does this PR follow our AI policy
(https://github.com/astral-sh/.github/blob/main/AI_POLICY.md)?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->
This reduces some unnecessary duplication across code that resolves a
module for a `from ...` import.

## Test Plan

This is a pure refactor that relies on existing test coverage.
<!-- How was it tested? -->
…7772)

This is the first prerequisite for fixing the performance issues in
astral-sh/ty#4269. It makes ty's existing shortcut for comparing
recursive protocols more accurate, without adding the later performance
optimizations.

## Keep the methods needed for comparison

A recursive protocol can send the type checker back through the same
requirements repeatedly. To avoid that, ty can first compare the
requirements that do not refer back to the protocol. The problem was
that it removed recursive-looking members from *both* types being
compared. A member of the actual type can mention the protocol even when
the corresponding requirement does not, so removing it loses information
needed to decide whether the types are compatible.

For example:

```python
from __future__ import annotations

from typing import Protocol

class Consumer[T](Protocol):
    def consume(self, value: T | int) -> None: ...

    @Property
    def child(self) -> Consumer[T]: ...

def extract[T](consumer: Consumer[T]) -> T:
    raise NotImplementedError

def check(value: Consumer[Consumer[int]]) -> None:
    reveal_type(extract(value))
    # Before: Consumer[int]
    # After:  Consumer[int] | int
```

Here, the actual `consume` method accepts `Consumer[int] | int`. Its
annotation mentions `Consumer`, so the old shortcut could discard the
method and infer only `Consumer[int]` from the class's type arguments.
Keeping the method available preserves the additional `int` possibility.

This PR filters only the requirements of the expected protocol, while
keeping every member of the actual type available to satisfy those
requirements. It also uses ordinary matching when the expected protocol
has no recursive requirements, even if the actual type contains a nested
use of the same protocol.

## Distinguish repeated aliases from recursion

ty tries to compare simple protocol members before members that can lead
back into another protocol comparison. Previously, any type alias caused
a member to be put in the potentially recursive group, even when the
alias was just another name for `int`.

The new traversal follows alias definitions before making that decision.
Reusing an ordinary alias does not itself create a cycle:

```python
type Identity[T] = T

# This expands completely to int.
type Example = Identity[Identity[int]]
```

But an actually recursive alias can keep changing its type arguments as
it expands:

```python
type Growing[T] = T | Growing[list[T]]

# Growing[int] leads to Growing[list[int]], then
# Growing[list[list[int]]], and so on.
```

For a recursive alias, the traversal stops when it reaches a definition
it is already expanding, even if the type arguments have changed.
Ordinary alias applications remain distinct, so nested uses such as
`Identity[Identity[int]]` are still recognized as non-recursive. ty can
answer this question without expanding the rest of a protocol's members.

## Test plan

The added protocol mdtests cover:

- A non-recursive expected protocol whose actual type contains a nested
use of that protocol.
- Recursive protocol members that contribute the full `Consumer[int] |
int` inference shown above.
- Nested applications of a non-recursive generic alias that must still
contribute to type inference.
- A recursive alias whose type arguments grow, checking that comparison
terminates and still rejects an incompatible assignment.
…icts (#27914)

## Summary

Follow-up to #27882.

Previously, changing deferred lookup modes while inferring `Annotated`
metadata could discard the enclosing string-annotation context, causing
parsed expressions to be looked up in the module's semantic index and
panic:

```python
from typing_extensions import Annotated, TypedDict

value: "Annotated[int, lambda default=int: None]"
other: "Annotated[int, TypedDict('T', {}, extra_items=int)]"
```

We now preserve `InStringAnnotation` while inferring lambda defaults.
For `TypedDict.extra_items`, we pass through the existing state and rely
on annotation inference to defer stub annotations without losing their
enclosing string context. This also covers nested lambdas, dynamic
classes in defaults, and positional-only and keyword-only stub defaults.

All 838 semantic tests pass.
## Summary

Follow-up to #27882.

Previously, the non-generic-class and `type[...]` fallback paths could
evaluate invalid subscript arguments inside string annotations, reaching
assignment expressions that are absent from the semantic index:

```python
value: "int[(other := 0)]"
another: "type[(other := 0)]"
```

We now skip expression inference in these error-recovery paths when the
arguments come from a string annotation. Non-generic classes retain
their existing `invalid-type-form` diagnostic, unsupported `type[...]`
forms retain their existing fallback type, and evaluated annotations
retain their runtime diagnostics. We do not add support for additional
`type[...]` forms.

This fix is independent of #27914 and #27913.
## Summary

Skip checking scripts with invalid PEP 723 metadata blocks, that are,
blocks that ty can't parse successfully or configurations that don't
resolve successfully. Instead, emit one diagnostic that points to the
invalid metadata.

ty still initializes a `Program` for each of those scripts so that LSP
operations continue to work. But we skip those scripts in all code paths
where we perform semantic checks.

Fixes astral-sh/ty#4181.

## Test plan

Added tests
## Summary

Add a CodSpeed baseline for checking a PEP 723 script that imports a
declared dependency. The following CLI integration PR uses the same
benchmark to measure warm-cache uv synchronization overhead and catch
regressions.

Testing: Ran the benchmark in the profiling profile and checked the
affected benchmark target.
## Summary

Enable the new automatic "sync script virtual environment" in ecosystem
runs. This ensures that scripts will be checked with their own virtual
environment and own dependencies (at least, once PRs later in this stack
land).

I decided to only enable the new scripts behavior because enabling the
new automatic uv sync for all projects breaks mypy primer, because uv
now tries to install all dependencies of that project which may not
succeed. We'll need to find a solution for this, but I prefer to solve
this later
## Summary

Previously, narrowing was lost after a statically known branch because
the unreachable control-flow path still contributed its original type:

```python
def narrow(value: int | None) -> None:
    if 1 + 1 == 2:
        if value is None:
            return

    reveal_type(value)  # Previously: int | None; now: int
```

We now gate narrowing constraints with their corresponding reachability
conditions and reuse predicate identities across both analyses. This
allows narrowing to propagate through constant conditions, short-circuit
expressions, match guards, and statically non-empty `range()` loops
while preserving ambiguous branches.

Statement-level calls now participate in normal reachability analysis in
every scope, replacing the separate scope-wide narrowing machinery for
module- and class-level calls. As a result, the improved narrowing also
works inside module- and class-level loops, while preserving
terminal-call analysis, overload selection, and loop-carried forward
references.

Because these calls now contribute to reachability graphs, we raise the
nested-binding cutoff from 2,048 to 4,096 to preserve precision in
call-heavy scopes. The loop-reachability and loop-inference cutoffs
remain unchanged to protect performance on loop-heavy projects such as
isort and parso.

We also track the places each predicate can narrow, avoiding unnecessary
constraint inference and preventing unrelated reachability gates from
introducing cycles through deferred annotations. Lambda and generator
conditions are resolved without inferring their bodies, avoiding
recursive-callable inference cycles.

This also fixes exhaustive nested checks where proving that an outer
branch terminates depends on narrowing a separate inner value,
preventing spurious `assert_never` errors.

Based on @mtshiba's work in #27207 and #23201.

Closes astral-sh/ty#4240.

---------

Co-authored-by: Shunsuke Shibayama <[email protected]>
Summary
--

Addresses
astral-sh/ruff#27666 (comment),
where we want to
start using "group" to refer to secondary categories. This type is
already referred to as `status`
in the user-facing JSON output, so this change aligns the internal type
name with that usage.

Test Plan
--

Existing tests
Generic classes inheriting recursive protocols can repeatedly expand the
same protocol interface when contextual constructor inference or
diagnostic collection binds explicitly constrained method receivers.
Programs with several inherited recursive members then become
pathologically slow.

Check finite requirements first for nominally inherited protocols, and
stop expanding recursive requirements once their constraints establish
the inherited relationship. Preserve full structural checking when it
remains necessary for erased type parameters, overridden requirements,
concrete-source inference, or diagnostic explanations. Also restrict
eager receiver-compatibility checks to overload selection, where they
actually prune candidates, so single-method binding does not recursively
re-enter the protocol.

Part of astral-sh/ty#4269.

## Ecosystem impact

Werkzeug gains two overload errors and two unused-ignore warnings
because improved contextual inference preserves the actual `sorted`
callback argument instead of widening it to `object`. This exposes its
existing `int | None` sort key; the old lambda-line ignore no longer
covers the call-level error.

## Test plan

- Protocol mdtests cover contextual constructor inference, invalid
constructor arguments, empty-iterable `Never` inference,
concrete/symbolic/unknown constrained receivers, erased type parameters
recovered structurally, and overridden recursive requirements.
- A diagnostic snapshot preserves nested protocol-member explanations
during context collection.
- A constraint-support test verifies exact type-variable identities
without treating declaration-default metadata as semantic support.
- Separate benchmarks cover recursive-protocol constructor inference and
diagnostic collection.
## Summary

Previously, invalid `Literal` and `LiteralString` arguments inside
string annotations could reach assignment expressions that are absent
from the semantic index:

```python
from typing import Literal, LiteralString

value: "LiteralString[(other := 0)]"
another: "Literal[int[(other := 0)]]"
```

We now infer both sides of assignment expressions in string annotations
without looking up nonexistent semantic-index definitions. This avoids
the panic while preserving unresolved-reference and other nested
diagnostics for invalid `Literal` and `LiteralString` arguments.
Evaluated annotations retain their existing inference and runtime
diagnostics.
Summary
--

Before this, running `./scripts/release.sh` produced this error on my
machine:

```console
❯ ./scripts/release.sh
Updating metadata with rooster...
warning: `VIRTUAL_ENV=/Users/brw/.virtualenvs/openai` does not match the project environment path `.venv` and will be ig
nored; use `--active` to target the active environment instead
error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided.

hint: To update the lockfile, run `uv lock`.
```

Passing an additional `--default-index` flag to the initial `uv run`
also works, but it seems easier
to export the environment variable for the whole workflow.

Test Plan
--

Manual testing in today's release
Summary
--

This aligns the pre-commit hook with our release script

## Test plan

Manual test on today's release branch
Sometimes Codex will write prose that assumes familiarity with the
current session, a specific PR change, or the issue that triggered it.
Add general guidance for writing mdtests, code comments, documentation,
PR descriptions, and GitHub issues for their eventual readers, grounded
in the final code and verified evidence.

Keep mdtest-specific advice to explain behavior and its rationale
directly, including a preference for "We reject this assignment" over
"We must reject this assignment." Clarify that common behavior comes
before specialized regressions, and that coherent subsections can be
long when they contain short examples interspersed with prose.

## Test plan

Guidance-only change; no runtime behavior or mdtest scenarios were
added. File-scoped documentation hooks pass.
Summary
--

This partially reverts astral-sh/ruff#27935 as
an alternative to enabling
preview everywhere. Preview was initially enabled ~2 years ago (#11496),
and we probably want to be
more selective about preview features now.

Test Plan
--

Today's release, previous errors:

-
https://github.com/astral-sh/ruff/actions/runs/32390111175/job/96493893174?pr=27937
-
https://github.com/astral-sh/ruff/actions/runs/32390111175/job/96493998150?pr=27937
The playground release jobs run `npm run check` and the builds on the
runner that later receives `CF_API_TOKEN`. Move those commands into
unprivileged jobs and pass named, digest-verified static artifacts to
fresh `release-playground` jobs. The publishers reject symlinks, special
files, and Pages server-code entrypoints before deployment. This
isolates the playground build tools; it does not remove the publisher's
Wrangler dependency. Locking that tooling is handled separately in
astral-sh/ruff#27838.

Related: astral-sh/ruff-vscode#1152 and astral-sh/ty-vscode#526 reduce
the dependency trees installed by the marketplace publishers.

---------

Co-authored-by: zaniebot <[email protected]>
sharkdp and others added 17 commits September 3, 2026 19:45
## Summary

Fix the subtype check in [Alex's
example](astral-sh/ruff#28246 (review)):
we treat `InvalidOverride` as a subtype, even though it overrides `read`
incompatibly.

```python
class Bounded[T: str](Protocol):
    def read(self) -> T: ...
    def other(self) -> Any: ...

class InvalidOverride(Bounded[str]):
    read = None

static_assert(is_subtype_of(InvalidOverride, Top[Bounded[Any]]))
```

## Test strategy

New Markdown tests
Summary
--

This should avoid requesting review from owners of these crates for the
automated version bumps,
assuming they don't want to be notified :)

Test Plan
--

Future releases
…tring (`ISC003`) (#27981)

Fixes #27979

Removing the `+` from the first statement of a module, function, or
class body turns the concatenation into a docstring, changing `__doc__`
and program behavior. Detect docstring positions and emit the fix with
unsafe safety level instead.

- Concats of plain string literals only (f-strings, bytes, t-strings can
never be docstrings)
- Covers module, function, class, and method bodies
- Safe fix behavior unchanged for non-docstring positions

## Test Plan

`cargo test -p ruff_linter --lib` (2813 pass), clippy + fmt clean

---------

Co-authored-by: Brent Westbrook <[email protected]>
Calling `dict(a=1)` can panic when a first-party `typing.py` hides
typeshed's generic definitions. Preserve key/value indices during
fallback inference and recover with `Unknown` after checking the values,
avoiding a second inference pass. Empty calls and specialization
failures retain ordinary constructor validation so their diagnostics are
preserved.

Fixes astral-sh/ty#4454.

## Test plan

Added mdtests for one and multiple named keywords with an empty
`typing.py`, diagnostics in keyword values, empty calls to a non-generic
dictionary requiring an argument, and a bounded dictionary value type
that reports both an invalid argument and a later unresolved name.
String member lookup can panic when a first-party `typing.py` introduces
an inference cycle through class and metaclass resolution. Add cycle
recovery to `known_class_to_instance` so inference can converge and
checking reports the ordinary diagnostics.

Fixes astral-sh/ty#4456.

## Test plan

Add mdtests with a local `typing.py` whose function call requires
resolving an unknown metaclass before a `Sequence` binding. Check the
consumer both before and after the shadowing module. Both scenarios
verify that `str.encode()` retains its `bytes` return type and that the
unresolved metaclass produces the expected diagnostic.
…straints (#28297)

During fixpoint iteration, we use argument types inferred from previous
iterations as type context for subsequent iterations. Arguments being
forwarded to a `ParamSpec` are initially inferred without type context,
as the type of the callable has not yet been inferred, and so we clear
their types after the initial iteration. However, we currently fallback
to `Unknown` as type context for arguments that have not yet been
inferred, even if they have been intentionally cleared, which can
pollute the types inferred in the next iteration. We should instead
avoid providing any type context in this case.

```py
from typing import TypedDict, Callable

class Payload(TypedDict):
    x: int

def forward[**P](function: Callable[P, None], /, *args: P.args, **kwargs: P.kwargs) -> None:
    function(*args, **kwargs)

def pair[T](first: T, second: T) -> None: ...
def _(payload: Payload):
    forward(pair, reveal_type({"x": 1}), payload)  # main: dict[str, int], fixed: Payload
```
## Summary

Projects that extend the same Ruff configuration repeatedly parse its
options and compile inherited per-file globs during file discovery.
Reuse that work within each traversal to reduce configuration overhead
when checking many projects.

Cache parsed and normalized configurations by file path and
normalization root, before inheritance merging and CLI overrides. Share
per-file glob compilation across configuration clones, preserving the
existing post-override compilation order and matching behavior. Reuse
path candidates during matching and skip empty per-file target-version
lookups.

The cache lasts for one traversal; later traversals reload
configurations. Parsing stays outside the cache lock so unrelated files
can load in parallel. Concurrent misses can load the same file more than
once.

## Test Plan

Existing CLI and workspace regressions pass for inherited configuration
errors, relative paths, CLI overrides, negated per-file ignores, and
target versions for linting and formatting. No snapshots changed.

---------

Co-authored-by: Charlie Marsh <[email protected]>
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
- Does this PR follow our AI policy
(https://github.com/astral-sh/.github/blob/main/AI_POLICY.md)?
-->

## Summary

As previewed in
astral-sh/ruff#27834 (comment),
this introduces a semantic model of the tests that pytest recognizes in
a particular file or for a particular candidate function. This model
will ultimately be shared by several features (e.g.,
astral-sh/ty#4119 and
astral-sh/ty#4041), and it is [immediately
integrated into our semantic model for pytest
fixtures](astral-sh/ruff#27974) (which powers
"Go to definition" and "Find references" in the language server).

## Test Plan

See included tests.
<!-- How was it tested? -->
… collection model (#27974)

<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
- Does this PR follow our AI policy
(https://github.com/astral-sh/.github/blob/main/AI_POLICY.md)?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->
This incorporates the shared model for pytest test collection into the
model of pytest fixture bindings which powers "Go to definition" and
"Find references" on test parameters in the language server.

## Test Plan

This is a refactor that relies on existing test coverage.
<!-- How was it tested? -->
## Summary

Linux release smoke tests currently install packages on the runner or in
containers with writable workspace mounts. Run them through the shared
[release-smoke-test
action](astral-sh/github-actions#1), which
mounts completed artifacts read-only.

Keep the existing smoke commands and target exclusions, and pin the test
images. Select the installed Rust toolchain as the container default so
pip can invoke Cargo from its temporary build directories.

## Test plan

- [x] Ensure the PR's Build binaries jobs run and succeed, including
sdist and wheel smoke tests using the pinned shared action on native and
emulated architectures.
We use callable types to represent the values that a `ParamSpec` can
specialize to. They only bind the parameters of a signature, though, not
its return type annotation. So we normalize `paramspec_value` callables
by removing their return type.

Internally, missing return types are treated the same as `Unknown`. This
means we were erroneously considering _all_ `paramspec_value` callables
to be dynamic, since they contained a dynamic return type, even if all
of the parameter annotations were fully static.

To fix this, I updated our default `TypeVisitor` `walk_callable` logic
to skip the return type entirely for `paramspec_value` callables, since
they are genuinely missing, not just implicitly `Unknown`. (I think we
use to model this separately, by storing the return type as an
`Option<Type>`, but going back to that seemed to be a deeper change than
was warranted.)
When inferring a callable against a declared callable type with a
`ParamSpec`, constraints from the outer assignability check may leak
their way into the inferred `ParamSpec` type. This can happen
particularly with callables with a declared return types of `Any`. For
example:
```py
from typing import Callable, Any

class Wrapper[**P]:
    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Any:
        raise NotImplementedError

def decorate[**P](callback: Callable[P, Any]) -> Wrapper[P]:
    raise NotImplementedError

def _[T](callback: Callable[[T], T]) -> None:
    reveal_type(decorate(callback))  # revealed: Wrapper[((T@_, /)) | ((Any, /))]
```

On astral-sh/ruff#26873, the same problem arises
with callable-scoped type variables.

This PR adds a very narrow short-circuit to ignore the gradual
constraint in this particular case. The more principled fix probably
resolves around [the quantification of signature-local
variables](https://github.com/astral-sh/ruff/blob/3070999871911cf59dca7ecd9e453d984314924c/crates/ty_python_semantic/src/types/signatures.rs#L2402)
which [`ParamSpec` inference currently
bypasses](https://github.com/astral-sh/ruff/blob/3070999871911cf59dca7ecd9e453d984314924c/crates/ty_python_semantic/src/types/signatures.rs#L2273),
as the naive quantification would lose constraints that should correctly
be applied to the inferred `ParamSpec`.

cc @dhruvmanila

---------

Co-authored-by: David Peter <[email protected]>
## Summary

We retain separate binding and declaration tables for every scope in a
file, even when those tables contain identical scope-local IDs. The
duplicate storage accounts for a measurable share of the semantic index
in representative projects.

We now share equivalent immutable tables across scopes while keeping
each scope's definitions and constraints in its own `UseDefMap`. The
interner is local to semantic-index construction, so its lookup sets do
not remain in the retained index.

Co-authored-by: Charlie Marsh <[email protected]>
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
- Does this PR follow our AI policy
(https://github.com/astral-sh/.github/blob/main/AI_POLICY.md)?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->
This moves the semantic model for pytest fixtures from
`dedicated/pytest.rs`[^1] to `dedicated/pytest/fixtures.rs`, where it is
now a sibling of other pytest sub-models, such as the test collection
model at `dedicated/pytest/collection.rs`.

[^1]: (in `crates/ty_python_semantic/src/types`)

## Test Plan

This is a refactor that relies on existing test coverage.
<!-- How was it tested? -->
370 commits from astral-sh/ruff (aug 12 → sep 4 2026). the typeshed is
regenerated from upstream's `.pyi` rather than merged.

the refactors that needed the fork's side rewritten rather than re-applied:

variance is now a lazy equation (`VarianceTerm`) instead of an eagerly
computed `TypeVarVariance`. `bivariant-private-attributes` rides on a keyed
`variance_equation_with(db, typevar, flag)`, and `typevar_is_unused` reads the
same equation with the flag off — which is what keeps the spec's
"bivariant means unused" fallback separable from the fork's rule.

`visit_expr` became `visit_expr_with_context`, so a condition and a value are
told apart while the index is built. the fork's six additions moved into the
new function, and `visit_if_condition` picks value context for an `if let`
subject and condition context otherwise.

a PEP 723 script's settings are resolved in `script.rs` now. the fork's rule —
the script's block layers over the project's configuration rather than
replacing it — is re-implemented in `resolve_script_options`, and
`[tool.basedpython]` reads there as it does everywhere else. a per-file
`[[overrides]]` block no longer reaches a script.

the ABC relocation waits for the pep 695 conversion. it moves classes as
source text, and reading them before `typing` was converted put 21 legacy
`TypeVar` classes in `_collections_abc` referring to declarations that the
conversion had already deleted.

every lint rule now declares a category; the `BY` rules are `Style`.

Co-Authored-By: Claude Opus 5 <[email protected]>
@KotlinIsland
KotlinIsland force-pushed the merge-upstream branch 3 times, most recently from 95510f0 to 3127563 Compare September 5, 2026 21:50
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

ecosystem check

Linter (stable)

ℹ️ ecosystem check detected linter changes. (+11 -2 violations, +0 -0 fixes in 3 projects; 56 projects unchanged)

apache/airflow (+8 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --no-preview --select ALL

+ devel-common/src/tests_common/test_utils/logging_command_executor.py:37:20: S603 `subprocess` call: check for execution of untrusted input
+ devel-common/src/tests_common/test_utils/logging_command_executor.py:41:14: S603 `subprocess` call: check for execution of untrusted input
+ devel-common/src/tests_common/test_utils/logging_command_executor.py:59:14: S603 `subprocess` call: check for execution of untrusted input
+ devel-common/src/tests_common/test_utils/logging_command_executor.py:86:20: S603 `subprocess` call: check for execution of untrusted input
+ devel-common/src/tests_common/test_utils/logging_command_executor.py:90:14: S603 `subprocess` call: check for execution of untrusted input
+ providers/edge3/src/airflow/providers/edge3/example_dags/win_test.py:226:27: S602 `subprocess` call with `shell=True` identified, security issue
+ providers/google/src/airflow/providers/google/cloud/operators/gcs.py:1029:18: S603 `subprocess` call: check for execution of untrusted input
+ providers/google/src/airflow/providers/google/cloud/operators/gcs.py:711:18: S603 `subprocess` call: check for execution of untrusted input

bokeh/bokeh (+2 -2 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --no-preview --select ALL

+ src/bokeh/server/asgi.py:391:21: ASYNC240 Async functions should not perform blocking pathlib.Path operations
- src/bokeh/server/asgi.py:391:21: ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
+ src/bokeh/server/views/static_handler.py:145:16: ASYNC240 Async functions should not perform blocking os.path operations
- src/bokeh/server/views/static_handler.py:145:16: ASYNC240 Async functions should not use os.path methods, use trio.Path or anyio.path

zulip/zulip (+1 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --no-preview --select ALL

+ zerver/openapi/javascript_examples.py:22:14: S607 Starting a process with a partial executable path

Changes by rule (4 rules affected)

code total + violation - violation + fix - fix
S603 7 7 0 0 0
ASYNC240 4 2 2 0 0
S602 1 1 0 0 0
S607 1 1 0 0 0

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+2327 -162 violations, +0 -0 fixes in 29 projects; 30 projects unchanged)

aiven/aiven-client (+13 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ aiven/client/argx.py:199:14: subclass-builtin Subclassing `dict` can be error prone, use `collections.UserDict` instead
+ aiven/client/argx.py:74:22: if-exp-instead-of-or-operator [*] Replace ternary `if` expression with `or` operator
+ aiven/client/cli.py:4501:46: unnecessary-dict-comprehension-for-iterable [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead
+ aiven/client/cli.py:6654:34: unnecessary-dict-comprehension-for-iterable [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead
+ aiven/client/cli.py:6765:34: unnecessary-dict-comprehension-for-iterable [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead
+ aiven/client/client.py:142:54: if-exp-instead-of-or-operator [*] Replace ternary `if` expression with `or` operator
+ aiven/client/client.py:856:20: if-exp-instead-of-or-operator [*] Replace ternary `if` expression with `or` operator
+ aiven/client/client.py:880:20: if-exp-instead-of-or-operator [*] Replace ternary `if` expression with `or` operator
+ aiven/client/connection_info/_utils.py:47:13: repeated-append Use `bits.extend((":", urllib.parse.quote(password)))` instead of repeatedly calling `bits.append()`
+ tests/test_cli.py:1505:5: pytest-raises-with-multiple-statements `pytest.raises()` block should contain a single simple statement
... 3 additional changes omitted for project

PlasmaPy/PlasmaPy (+26 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ docs/_author_list_from_cff.py:97:9: missing-f-string-syntax Possible f-string without an `f` prefix
+ docs/notebooks/formulary/ExB_drift.ipynb:cell 15:3:24: missing-f-string-syntax Possible f-string without an `f` prefix
+ docs/notebooks/formulary/ExB_drift.ipynb:cell 15:4:30: missing-f-string-syntax Possible f-string without an `f` prefix
+ src/plasmapy/analysis/fit_functions.py:584:12: float-equality-comparison Unreliable floating point equality comparison `m == 0.0`
+ src/plasmapy/analysis/swept_langmuir/floating_potential.py:243:17: float-equality-comparison Unreliable floating point equality comparison `current == 0.0`
+ src/plasmapy/formulary/quantum.py:123:10: float-equality-comparison Unreliable floating point equality comparison `0 * u.m / u.s == V`
+ src/plasmapy/plasma/grids.py:1373:26: float-equality-comparison Unreliable floating point equality comparison `norms == 0.0`
+ tests/analysis/test_fit_functions.py:534:16: float-equality-comparison Unreliable floating point equality comparison `root == np.log(5.0 / 3.0) / 0.5`
+ tests/formulary/test_fusion.py:326:16: float-equality-comparison Unreliable floating point equality comparison `sigma.unit == u.m**3 / u.s`
... 17 additional changes omitted for rule float-equality-comparison
+ tests/formulary/test_quantum.py:122:9: missing-f-string-syntax Possible f-string without an `f` prefix
... 16 additional changes omitted for project

apache/airflow (+592 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ airflow-core/tests/conftest.py:166:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/system/conftest.py:34:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/always/test_providers_manager.py:350:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/always/test_providers_manager.py:93:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/always/test_secrets_environment_variables.py:62:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ airflow-core/tests/unit/api/common/test_airflow_health.py:308:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
... 579 additional changes omitted for rule pytest-fixture-autouse
+ devel-common/src/tests_common/test_utils/logging_command_executor.py:37:20: subprocess-without-shell-equals-true `subprocess` call: check for execution of untrusted input
+ devel-common/src/tests_common/test_utils/logging_command_executor.py:41:14: subprocess-without-shell-equals-true `subprocess` call: check for execution of untrusted input
+ devel-common/src/tests_common/test_utils/logging_command_executor.py:59:14: subprocess-without-shell-equals-true `subprocess` call: check for execution of untrusted input
+ devel-common/src/tests_common/test_utils/logging_command_executor.py:86:20: subprocess-without-shell-equals-true `subprocess` call: check for execution of untrusted input
+ devel-common/src/tests_common/test_utils/logging_command_executor.py:90:14: subprocess-without-shell-equals-true `subprocess` call: check for execution of untrusted input
... 581 additional changes omitted for project

apache/superset (+112 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ tests/integration_tests/celery_tests.py:71:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/charts/api_tests.py:94:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/charts/data/api_tests.py:131:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/charts/version_restore_tests.py:68:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/conftest.py:120:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/dao/base_dao_test.py:72:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/dao/conftest.py:46:33: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/dashboards/filter_state/api_tests.py:56:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/dashboards/update_tabs_test.py:41:17: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/integration_tests/dashboards/version_restore_tests.py:69:21: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
... 102 additional changes omitted for project

binary-husky/gpt_academic (+140 -34 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

- crazy_functions/Academic_Conversation.py:162:28: call-datetime-now-without-tzinfo `datetime.datetime.now()` called without a `tz` argument
+ crazy_functions/Conversation_To_File.py:199:17: if-exp-instead-of-or-operator [*] Replace ternary `if` expression with `or` operator
+ crazy_functions/Conversation_To_File.py:56:28: unnecessary-enumerate `enumerate` index is unused, use `for x in y` instead
+ crazy_functions/Document_Conversation.py:289:9: return-in-generator Using `yield` and `return {value}` in a generator function can lead to confusing behavior
+ crazy_functions/Document_Conversation.py:337:9: return-in-generator Using `yield` and `return {value}` in a generator function can lead to confusing behavior
+ crazy_functions/Document_Conversation.py:438:17: return-in-generator Using `yield` and `return {value}` in a generator function can lead to confusing behavior
+ crazy_functions/Document_Optimize.py:112:13: return-in-generator Using `yield` and `return {value}` in a generator function can lead to confusing behavior
+ crazy_functions/Document_Optimize.py:359:13: return-in-generator Using `yield` and `return {value}` in a generator function can lead to confusing behavior
+ crazy_functions/Document_Optimize.py:448:9: return-in-generator Using `yield` and `return {value}` in a generator function can lead to confusing behavior
... 41 additional changes omitted for rule return-in-generator
+ crazy_functions/Image_Generate.py:183:20: if-exp-instead-of-or-operator Replace ternary `if` expression with `or` operator
... 164 additional changes omitted for project

bokeh/bokeh (+4 -2 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ src/bokeh/server/asgi.py:391:21: blocking-path-method-in-async-function Async functions should not perform blocking pathlib.Path operations
- src/bokeh/server/asgi.py:391:21: blocking-path-method-in-async-function Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
+ src/bokeh/server/views/static_handler.py:145:16: blocking-path-method-in-async-function Async functions should not perform blocking os.path operations
- src/bokeh/server/views/static_handler.py:145:16: blocking-path-method-in-async-function Async functions should not use os.path methods, use trio.Path or anyio.path
+ tests/test_examples.py:112:34: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators
+ tests/unit/bokeh/io/test_export.py:104:33: pytest-fixture-autouse Avoid using `autouse=True` in `pytest.fixture` decorators

docker/docker-py (+47 -5 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ docker/api/build.py:20:9: escape-sequence-in-docstring Use `r"""` if any backslashes in a docstring
+ docker/api/client.py:472:35: float-equality-comparison Unreliable floating point equality comparison `timeout == 0.0`
+ docker/auth.py:75:18: subclass-builtin Subclassing `dict` can be error prone, use `collections.UserDict` instead
+ docker/context/context.py:76:16: unnecessary-dunder-call Unnecessary dunder call to `__call__`
+ docker/models/containers.py:537:9: escape-sequence-in-docstring Use `r"""` if any backslashes in a docstring
+ docker/types/base.py:1:16: subclass-builtin Subclassing `dict` can be error prone, use `collections.UserDict` instead
+ docker/types/containers.py:264:18: subclass-builtin Subclassing `dict` can be error prone, use `collections.UserDict` instead
+ docker/types/containers.py:688:23: subclass-builtin Subclassing `dict` can be error prone, use `collections.UserDict` instead
+ docker/types/networks.py:54:24: subclass-builtin Subclassing `dict` can be error prone, use `collections.UserDict` instead
+ docker/types/networks.py:5:22: subclass-builtin Subclassing `dict` can be error prone, use `collections.UserDict` instead
... 22 additional changes omitted for rule subclass-builtin
... 42 additional changes omitted for project

facebookresearch/chameleon (+5 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ chameleon/inference/chameleon.py:387:26: slice-copy [*] Prefer `copy` method over slicing
+ chameleon/inference/stopping_criteria.py:16:28: subclass-builtin Subclassing `list` can be error prone, use `collections.UserList` instead
+ chameleon/inference/vqgan.py:92:32: float-equality-comparison Unreliable floating point equality comparison `temp == 1.0`
+ chameleon/viewer/backend/models/chameleon_distributed.py:394:17: logging-eager-conversion Unnecessary `str()` conversion when formatting with `%s`
+ chameleon/viewer/backend/models/chameleon_distributed.py:475:17: logging-eager-conversion Unnecessary `str()` conversion when formatting with `%s`

... Truncated remaining completed project reports due to GitHub comment length restrictions

Changes by rule (56 rules affected)

code total + violation - violation + fix - fix
pytest-fixture-autouse 750 750 0 0 0
pytest-raises-with-multiple-statements 232 232 0 0 0
float-equality-comparison 148 148 0 0 0
pytest-raises-ambiguous-pattern 143 143 0 0 0
while-one 122 122 0 0 0
numpy-legacy-random 98 98 0 0 0
blocking-path-method-in-async-function 94 92 2 0 0
unnecessary-dunder-call 75 75 0 0 0
call-datetime-without-tzinfo 74 0 74 0 0
if-exp-instead-of-or-operator 70 70 0 0 0
repeated-append 61 61 0 0 0
call-datetime-now-without-tzinfo 58 0 58 0 0
unsorted-imports 56 56 0 0 0
return-in-generator 51 51 0 0 0
missing-f-string-syntax 42 42 0 0 0
unnecessary-lambda 41 41 0 0 0
subclass-builtin 39 39 0 0 0
legacy-form-pytest-raises 36 36 0 0 0
slice-copy 26 26 0 0 0
unnecessary-enumerate 25 25 0 0 0
escape-sequence-in-docstring 25 25 0 0 0
none-not-at-end-of-union 23 23 0 0 0
unnecessary-dict-comprehension-for-iterable 22 22 0 0 0
math-constant 15 15 0 0 0
missing-maxsplit-arg 12 12 0 0 0
single-item-membership-test 12 12 0 0 0
fallible-context-manager 11 11 0 0 0
for-loop-set-mutations 10 10 0 0 0
logging-eager-conversion 9 9 0 0 0
pytest-patch-with-lambda 9 9 0 0 0
exec-builtin 9 0 9 0 0
needless-else 8 8 0 0 0
reimplemented-builtin 8 8 0 0 0
unused-noqa 8 8 0 0 0
meta-class-abc-meta 8 8 0 0 0
noqa-comments 8 0 8 0 0
subprocess-without-shell-equals-true 7 7 0 0 0
useless-finally 7 7 0 0 0
hardcoded-string-charset 5 5 0 0 0
call-datetime-strptime-without-zone 5 0 5 0 0
call-date-today 4 0 4 0 0
loop-iterator-mutation 3 3 0 0 0
fast-api-non-annotated-dependency 3 3 0 0 0
redundant-bool-literal 3 3 0 0 0
falsy-dict-get-fallback 2 2 0 0 0
call-datetime-fromtimestamp 2 0 2 0 0
subprocess-popen-with-shell-equals-true 1 1 0 0 0
trailing-comma-on-bare-tuple 1 1 0 0 0
unnecessary-regular-expression 1 1 0 0 0
blocking-input-in-async-function 1 1 0 0 0
unnecessary-if 1 1 0 0 0
pandas-use-of-dot-is-null 1 1 0 0 0
abstract-base-class-without-abstract-method 1 1 0 0 0
empty-method-without-abstract-decorator 1 1 0 0 0
implicit-class-var-in-dataclass 1 1 0 0 0
start-process-with-partial-path 1 1 0 0 0

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

✅ ecosystem check detected no format changes.

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

by ecosystem round-trip

base: bc69ba29ef45959c7d21d6541d568483e414c643 (merge base) → head: 201/merge

regressions: 1, changed: 715, improvements: 2, error changes: 2 (across 25977 files in 148 projects)

⚠️ 10 project(s) fail to round-trip on both base and head, so this check says nothing about them.

❌ regressions (built on base, now fails)

dedupe —
build: killed: timed out after 900s

ℹ️ changed round-trip output

Expression — _by_sourcemap.py
--- base/_by_sourcemap.py
+++ head/_by_sourcemap.py
@@ -78,9 +78,7 @@
... 28975 characters elided ...
     "/tmp/tmpuegk30i1/Expression/out/tests/test_tagged_union.py": {"by": "sha256:adf68fcc4ac3ff05dfb3b9f856355c7bbec9f50b083417a79fe6be7f78e8bdb0", "py": "sha256:dbe410640a0b1872437c0134d78d55fd66740c68a39d0ca63df5c6f42bc757f8"},
Expression — tests/test_block.py
--- base/tests/test_block.py
+++ head/tests/test_block.py
@@ -323,5 +323,5 @@
     mapper: Callable[[int, int], int] = lambda x, y: x + y
     ys = _soundness_parametric(pipe(
-        _soundness_check(block.of_seq(xs), Block),
+        _soundness_parametric(block.of_seq(xs), Block[tuple[int, int]], (0,)),
         block.starmap(mapper),
     ), Block[int], (0,))
@@ -335,5 +335,5 @@
     mapper: Callable[[int, int], int] = lambda x, y: x + y
     ys = _soundness_parametric(pipe(
-        _soundness_check(block.of_seq(xs), Block),
+        _soundness_parametric(block.of_seq(xs), Block[tuple[int, int]], (0,)),
         block.map2(mapper),
     ), Block[int], (0,))
@@ -347,5 +347,5 @@
     mapper: Callable[[int, int, int], int] = lambda x, y, z: x + y + z
     ys = _soundness_parametric(pipe(
-        _soundness_check(block.of_seq(xs), Block),
+        _soundness_parametric(block.of_seq(xs), Block[tuple[int, int, int]], (0,)),
         block.map3(mapper),
     ), Block[int], (0,))
Expression — tests/test_option_builder.py
(only produced on base)
Expression — tests/test_result_builder.py
(only produced on base)
Expression — tests/test_seq.py
--- base/tests/test_seq.py
+++ head/tests/test_seq.py
@@ -510,10 +510,10 @@
         nonlocal ran
         ran = True
-        return _soundness_parametric(seq.of_list(xs), Seq[int], (0,))
+        return _soundness_check(seq.of_list(xs), Seq)
 
     ys = seq.delay(generator)
     assert not ran
 
-    assert list[int](ys) == xs
+    assert list(ys) == xs
     assert ran
PyGithub — _by_sourcemap.py
--- base/_by_sourcemap.py
+++ head/_by_sourcemap.py
@@ -518,5 +518,5 @@
     "/tmp/tmp3hirm2hw/PyGithub/out/openapi/conftest.py": {"by": "sha256:b7b3ce9254deaa83ee4787b0ee0511658b3a0d1aec5321906f0c6e3dc323dc38", "py": "sha256:d3a19b3f9de87d52196b54d6b4ab1467a3601a020a1e46ddbc1ce708e18dbee4"},
     "/tmp/tmp3hirm2hw/PyGithub/out/scripts/fix_headers.py": {"by": "sha256:105094085c5acc16071f1de2451593a71500faa1ed75d5af681744f7732b0a38", "py": "sha256:e18e7a20a9b069e1b54d9481ccc0e6332d7418a13bc98c236dcefe35b571c178"},
-    "/tmp/tmp3hirm2hw/PyGithub/out/scripts/openapi.py": {"by": "sha256:d38c9629bddaf8736664aff189f68a6721810b1f0a495d4554f1a26e85941096", "py": "sha256:2f39dacd11a5838538661d896370a5715355cdefac780cd65563973ee778ff6a"},
+    "/tmp/tmp3hirm2hw/PyGithub/out/scripts/openapi.py": {"by": "sha256:d38c9629bddaf8736664aff189f68a6721810b1f0a495d4554f1a26e85941096", "py": "sha256:54a3d0548086ba275e1366a0829130e294c9bdb69410933e13af72db52aec02b"},
     "/tmp/tmp3hirm2hw/PyGithub/out/scripts/prepare-for-update-assertions.py": {"by": "sha256:dd1f8c58c01073da2d1c925f9ba265e4909ac9b3e6a2541b593af5b9cde3c906", "py": "sha256:08f68b18dd974244f15cb23c240fbb847e2bd6ada1140deb67a5c093b9fb9837"},
     "/tmp/tmp3hirm2hw/PyGithub/out/scripts/sort_class.py": {"by": "sha256:fbe974b2b75d3a1d06c7b1e1ccb8708c5d288c6c73facb249bacf2bb3fc13d17", "py": "sha256:1b1c011869c7abbf49ecff378cd589f3af680e393fead308d3a3680f45ea7b85"},
PyGithub — scripts/openapi.py
--- base/scripts/openapi.py
+++ head/scripts/openapi.py
@@ -2052,5 +2052,5 @@
                         prop = self.properties.pop(0)
                         stmt = create_statement(prop, self_attribute)
-                        stmts = _soundness_check(stmts[:i], list) + [stmt] + _soundness_check(stmts[i:], list)
+                        stmts = stmts[:i] + [stmt] + stmts[i:]
                         i = i + 1
                     if self.properties and self.properties[0].python_name == asserted_property:
@@ -3432,5 +3432,5 @@
             return []
 
-        for name, clazz in _soundness_check(sorted(classes.items(), key=lambda v: v[0]), list):
+        for name, clazz in _soundness_check(sorted(classes.items(), key=lambda v: _soundness_check(v[0], str)), list):
             if not clazz.get("ids", []):
                 base_ids = get_ids(name)
@@ -4758,5 +4758,5 @@
             type=HandleNewSchemas,
... 1285 characters elided ...
-            choices=list(HandleNewSchemas),
+            choices=list[HandleNewSchemas](HandleNewSchemas),
         )
         create_method_parser.add_argument(
Tanjun — _by_sourcemap.py
--- base/_by_sourcemap.py
+++ head/_by_sourcemap.py
@@ -30,5 +30,5 @@
... 58995 characters elided ...
     "/tmp/tmppmpt6tpp/Tanjun/out/tests/context/test_autocomplete.py": {"by": "sha256:01484543bee3deb8f43a0dc70f061f2a4352e7c8ccb0db4b1d167f1f16c276d4", "py": "sha256:bf745339b19923e500f1615b3b8e473a3032974e961a916b965bc409dbacf391"},
Tanjun — tanjun/clients.py
--- base/tanjun/clients.py
+++ head/tanjun/clients.py
@@ -445,5 +445,5 @@
     MessageAcceptsEnum.NONE: None,
 }
-assert _ACCEPTS_EVENT_TYPE_MAPPING.keys() == set(MessageAcceptsEnum)
+assert _ACCEPTS_EVENT_TYPE_MAPPING.keys() == set[MessageAcceptsEnum](MessageAcceptsEnum)
Tanjun — tanjun/commands/message.py
--- base/tanjun/commands/message.py
+++ head/tanjun/commands/message.py
@@ -270,5 +270,5 @@
     # <<inherited docstring from tanjun.abc.MessageCommand>>.
     def names(self) -> collections.Collection[str]:
-        return self._names.copy()
+        return _soundness_check(self._names.copy(), list)
 
     @property
@@ -307,5 +307,5 @@
         inst = super().copy()
         inst._callback = copy.copy(self._callback)  # noqa: SLF001
-        inst._names = self._names.copy()  # noqa: SLF001
+        inst._names = _soundness_check(self._names.copy(), list)  # noqa: SLF001
         inst._parent = parent  # noqa: SLF001
         inst._parser = self._parser.copy() if self._parser else None  # noqa: SLF001
@@ -451,5 +451,5 @@
     def commands(self) -> collections.Collection[tanjun.MessageCommand[typing.Any]]:
         # <<inherited docstring from tanjun.abc.MessageCommandGroup>>.
-        return self._commands.commands.copy()
+        return _soundness_check(self._commands.commands.copy(), list)
 
     @property
Tanjun — tanjun/commands/slash.py
--- base/tanjun/commands/slash.py
+++ head/tanjun/commands/slash.py
@@ -1204,5 +1204,5 @@
     def commands(self) -> collections.Collection[tanjun.BaseSlashCommand]:
         # <<inherited docstring from tanjun.abc.SlashCommandGroup>>.
-        return self._commands.copy().values()
+        return _soundness_check(self._commands.copy(), dict).values()
 
     @property
@@ -1722,15 +1722,15 @@
     def float_autocompletes(self) -> collections.Mapping[str, tanjun.AutocompleteSig[JustFloat]]:
         # <<inherited docstring from tanjun.abc.SlashCommand>>.
-        return self._float_autocompletes.copy()
+        return _soundness_check(self._float_autocompletes.copy(), dict)
 
     @property
     def int_autocompletes(self) -> collections.Mapping[str, tanjun.AutocompleteSig[int]]:
         # <<inherited docstring from tanjun.abc.SlashCommand>>.
-        return self._int_autocompletes.copy()
+        return _soundness_check(self._int_autocompletes.copy(), dict)
 
     @property
     def str_autocompletes(self) -> collections.Mapping[str, tanjun.AutocompleteSig[str]]:
... 1456 characters elided ...
         ] = {}
-        for tracked_option in self._tracked_options.values():
+        for tracked_option in _soundness_iter(self._tracked_options.values(), _TrackedOption):
             if not (option := ctx.options.get(tracked_option.name)):
                 if tracked_option.default is tanjun.NO_DEFAULT:
Tanjun — tanjun/components.py
--- base/tanjun/components.py
+++ head/tanjun/components.py
@@ -317,5 +317,5 @@
     ) -> collections.Mapping[type[hikari.Event], collections.Collection[tanjun.ListenerCallbackSig[typing.Any]]]:
         # <<inherited docstring from tanjun.abc.Component>>.
-        return _internal.CastedView(self._listeners, lambda x: x.copy())
+        return _internal.CastedView(self._listeners, lambda x: _soundness_check(x.copy(), list))
 
     @property
@@ -893,5 +893,5 @@
         # <<inherited docstring from tanjun.abc.Component>>.
         try:
-            del self._menu_commands[command.type, command.name]
+            del self._menu_commands[(command.type, command.name)]
         except KeyError:
             error_message = f"Command {command.name} not found"
Tanjun — tanjun/context/autocomplete.py
--- base/tanjun/context/autocomplete.py
+++ head/tanjun/context/autocomplete.py
@@ -206,5 +206,5 @@
     def options(self) -> collections.Mapping[str, hikari.AutocompleteInteractionOption]:
         # <<inherited docstring from tanjun.abc.AutocompleteContext>>.
-        return self._options.copy()
+        return _soundness_check(self._options.copy(), dict)
 
     async def fetch_channel(self) -> hikari.TextableChannel:
Tanjun — tanjun/context/slash.py
--- base/tanjun/context/slash.py
+++ head/tanjun/context/slash.py
@@ -1258,5 +1258,5 @@
     def options(self) -> collections.Mapping[str, tanjun.SlashOption]:
         # <<inherited docstring from tanjun.abc.SlashContext>>.
-        return self._options.copy()
+        return _soundness_check(self._options.copy(), dict)
 
     @property
Tanjun — tests/commands/test_slash.py
--- base/tests/commands/test_slash.py
+++ head/tests/commands/test_slash.py
@@ -1630,5 +1630,5 @@
         assert tracked.type is hikari.OptionType.STRING
         assert tracked.default == "ayya"
-        assert list(tracked.converters) == [mock_converter]
+        assert list[str](tracked.converters) == [mock_converter]
         assert tracked.is_always_float is False
         assert tracked.is_only_member is False
@@ -1885,5 +1885,5 @@
         command.add_str_option("boom", "No u", converters=Enum)
 
-        assert list(command._tracked_options["boom"].converters) == [Enum]
+        assert list[str](command._tracked_options["boom"].converters) == [Enum]
 
     def test_add_int_option(self, command: tanjun.SlashCommand[typing.Any]) -> None:
@@ -1910,5 +1910,5 @@
         assert tracked.type is hikari.OptionType.INTEGER
         assert tracked.default == "nya"
-        assert list(tracked.converters) == [mock_converter]
+        assert list[str](tracked.converters) == [mock_converter]
         assert tracked.is_always_float is False
         assert tracked.is_only_member is False
@@ -2106,5 +2106,5 @@
... 680 characters elided ...
         command.add_float_option("sesese", "asasasa", converters=[Enum])
 
-        assert list(command._tracked_options["sesese"].converters) == [Enum]
+        assert list[str](command._tracked_options["sesese"].converters) == [Enum]
 
     def test_add_bool_option(self, command: tanjun.SlashCommand[typing.Any]) -> None:
aiohttp — _by_sourcemap.py
--- base/_by_sourcemap.py
+++ head/_by_sourcemap.py
@@ -126,5 +126,5 @@
... 63804 characters elided ...
     "/tmp/tmpx7yx064n/aiohttp/out/tests/test_imports.py": {"by": "sha256:3872e2571b2a3015abc1ba81ffe1949a92788629170940480a9c1611db3f126b", "py": "sha256:bd14a250d50bccafb8c86d026c85532f545712e91a261a2a6f1a70adb814e85e"},
aiohttp — aiohttp/http_parser.py
--- base/aiohttp/http_parser.py
+++ head/aiohttp/http_parser.py
@@ -579,5 +579,5 @@
                         def get_content_length() -> int | None:
                             # payload length
-                            length_hdr = _soundness_check(msg.headers.get(CONTENT_LENGTH), (str, type(None)))
+                            length_hdr = msg.headers.get(CONTENT_LENGTH)
                             if length_hdr is None:
                                 return None
@@ -781,9 +781,9 @@
         # as the target tokens (close, keep-alive, upgrade) are simple ASCII
         # values that never contain commas.
-        conn_values = _soundness_check(headers.get(hdrs.CONNECTION), (str, type(None)))
+        conn_values = headers.get(hdrs.CONNECTION)
         if conn_values:
             conn_tokens = {
                 token.lower()
-                for token in _soundness_iter((part.strip(" \t") for part in _soundness_iter(conn_values.split(","), str)), str)
+                for token in (part.strip(" \t") for part in conn_values.split(","))
                 if token and token.isascii()
             }
... 292 characters elided ...
                 upgrade = True
 
@@ -804,5 +804,5 @@
 
         # chunking
-        te = _soundness_check(headers.get(hdrs.TRANSFER_ENCODING), (str, type(None)))
+        te = headers.get(hdrs.TRANSFER_ENCODING)
         if te is not None:
             if self._is_chunked_te(te):
aiohttp — aiohttp/multipart.py
--- base/aiohttp/multipart.py
+++ head/aiohttp/multipart.py
@@ -420,5 +420,5 @@
         # base64 decodes in quartets and every chunk is decoded on its own, so
         # a chunk should not end mid-quartet.
-        encoding = _soundness_check(self.headers.get(CONTENT_TRANSFER_ENCODING), (str, type(None)))
+        encoding = self.headers.get(CONTENT_TRANSFER_ENCODING)
         if encoding and encoding.lower() == "base64":
             chunk = self._align_base64_chunk(chunk, len(carry) + want)
aiohttp — aiohttp/payload.py
--- base/aiohttp/payload.py
+++ head/aiohttp/payload.py
@@ -662,5 +662,5 @@
                 self._read,
                 (
-                    min(DEFAULT_CHUNK_SIZE, remaining_content_len)
+                    _soundness_check(min(DEFAULT_CHUNK_SIZE, remaining_content_len), int)
                     if remaining_content_len is not None
                     else DEFAULT_CHUNK_SIZE
aiohttp — aiohttp/resolver.py
--- base/aiohttp/resolver.py
+++ head/aiohttp/resolver.py
@@ -82,5 +82,5 @@
                     # or IPv6 is not enabled in the host
                     continue
-                if address[3]:
+                if _soundness_check(address[3], int):
                     # This is essential for link-local IPv6 addresses.
                     # LL IPv6 is a VERY rare case. Strictly speaking, we should use
@@ -162,5 +162,5 @@
             address: tuple[bytes, int] | tuple[bytes, int, int, int] = node.addr
             if node.family == socket.AF_INET6:
-                if len(address) > 3 and address[3]:
+                if len(address) > 3 and _soundness_check(address[3], int):
                     # This is essential for link-local IPv6 addresses.
                     # LL IPv6 is a VERY rare case. Strictly speaking, we should use
aiohttp — aiohttp/test_utils.py
--- base/aiohttp/test_utils.py
+++ head/aiohttp/test_utils.py
@@ -798,5 +798,5 @@
     chunked = "chunked" in _soundness_check(headers.get(hdrs.TRANSFER_ENCODING, ""), str).lower()
     upgrade = _soundness_check(headers.get(hdrs.CONNECTION, ""), str).lower() == "upgrade" and bool(
-        _soundness_check(headers.get(hdrs.UPGRADE), (str, type(None)))
+        headers.get(hdrs.UPGRADE)
     )
aiohttp — tests/test_http_parser.py
--- base/tests/test_http_parser.py
+++ head/tests/test_http_parser.py
@@ -1,2 +1,3 @@
+lazy from typing import Any
 def _soundness_check(_v, _t):
     if not isinstance(_v, _t):
@@ -21,5 +22,4 @@
 lazy from collections.abc import Iterable, Iterator
 lazy from contextlib import suppress
-lazy from typing import Any
 lazy from unittest import mock
 lazy from urllib.parse import quote
aioredis — _by_sourcemap.py
--- base/_by_sourcemap.py
+++ head/_by_sourcemap.py
@@ -53,5 +53,5 @@
     "/tmp/tmpuktkhdd4/aioredis/out/aioredis/client.py": {"by": "sha256:4ab41d081d399ed2f84302b5c43bbd59d6b99e56a74fa365e2facf892d820ca5", "py": "sha256:03c2bf392aec9d2aba2ee42fe90c917038467358b1b72fa5287fa9934c915e38"},
     "/tmp/tmpuktkhdd4/aioredis/out/aioredis/compat.py": {"by": "sha256:ca994ab4387a0ac3beb16aa8c0b8321790f7930cdcc3b9e912394e180df8eb99", "py": "sha256:b9c2bf337c0264253577288a66b03ada923e6ce210ef6b079b445aa5213d58b9"},
-    "/tmp/tmpuktkhdd4/aioredis/out/aioredis/connection.py": {"by": "sha256:ae10a0dafd9c3b88ce20c41f6968205a7a6922e0f0603a3b93a400eb96f7a574", "py": "sha256:b161f0bae30bf6e339672838e4503266e1aa333d764841a1962835badd453906"},
+    "/tmp/tmpuktkhdd4/aioredis/out/aioredis/connection.py": {"by": "sha256:ae10a0dafd9c3b88ce20c41f6968205a7a6922e0f0603a3b93a400eb96f7a574", "py": "sha256:d54c46d327a47e9bb43cdf4c9a8e4deea47faac6207a20c87dd6df95b9dc771f"},
     "/tmp/tmpuktkhdd4/aioredis/out/aioredis/exceptions.py": {"by": "sha256:34839273b5deda8d51a37f7e36c7df94461526ec70c21d298e1e77556cda043e", "py": "sha256:478ed78e91d104099cf1ceedf74792496cbb8b1bc74aaf3728597ef5c0d34593"},
     "/tmp/tmpuktkhdd4/aioredis/out/aioredis/lock.py": {"by": "sha256:583c756be49f801fdebc0161f30ce908dc831488d14a9afaabd3d6a402ea2e35", "py": "sha256:f651077d8d35b26269fc614ff8d40e40f79f94ee728083392c22d125a11d84ee"},
aioredis — aioredis/connection.py
--- base/aioredis/connection.py
+++ head/aioredis/connection.py
@@ -203,5 +203,5 @@
             exception_class_or_dict = _soundness_check(self.EXCEPTION_CLASSES[error_code], (type, Mapping))
             if isinstance(exception_class_or_dict, dict):
-                exception_class = exception_class_or_dict.get(response, ResponseError)
+                exception_class = _soundness_check(exception_class_or_dict.get(response, ResponseError), type)
             else:
                 exception_class = exception_class_or_dict
aiortc — _by_sourcemap.py
--- base/_by_sourcemap.py
+++ head/_by_sourcemap.py
@@ -113,5 +113,5 @@
     "/tmp/tmpni9tmux_/aiortc/out/aiortc/rtcrtpsender.py": {"by": "sha256:526fc68e643666b7586f3ae658d35745b5c2581fca3bacf05c4130efbae701d2", "py": "sha256:5358af5b6d37759967aa0b4830d519d32e9617e6155819a202162c557947b4fe"},
     "/tmp/tmpni9tmux_/aiortc/out/aiortc/rtcrtptransceiver.py": {"by": "sha256:c88f0500173d9c86074f882d6ef2bb079e92423192b3512103484880da9b9b96", "py": "sha256:31a007254199f2dbd963533ed584e59e9a90be8e287939a6b27b2a0041f58fc0"},
-    "/tmp/tmpni9tmux_/aiortc/out/aiortc/rtcsctptransport.py": {"by": "sha256:06b91a0acc21b37e2580fd16a3bcf5b352fe2d04fc79b3c494e1952cfa06cd6b", "py": "sha256:065f7ab02e0eed156f851961527c2bfefb2e7b00f93548e75f02b909f5acfed0"},
+    "/tmp/tmpni9tmux_/aiortc/out/aiortc/rtcsctptransport.py": {"by": "sha256:06b91a0acc21b37e2580fd16a3bcf5b352fe2d04fc79b3c494e1952cfa06cd6b", "py": "sha256:116b6c0814a59a71c197fba7acfd94998ca8dce6f0c4fc9bfa60a2ad8d8e08ff"},
     "/tmp/tmpni9tmux_/aiortc/out/aiortc/rtcsessiondescription.py": {"by": "sha256:a22257c1fbdd5a000ad964972f4346478491eb5d21b8b9119dc6322526d6d2c6", "py": "sha256:7cb1f8ba62c1307a6f405911dd301b927a8bdbc92c8b83b3650b249fcf0650df"},
     "/tmp/tmpni9tmux_/aiortc/out/aiortc/rtp.py": {"by": "sha256:f562db2dbc71992bd6fafb2e5d722e13b3ac2c4320eae2a6ccc0efba3d8c6331", "py": "sha256:931573f9e6b155aa488b084c7ee0f7ec31d0a2fa69966a92749dbee0132b65e3"},
aiortc — aiortc/rtcsctptransport.py
--- base/aiortc/rtcsctptransport.py
+++ head/aiortc/rtcsctptransport.py
@@ -563,5 +563,5 @@
         start_pos = None
         while pos < len(self.reassembly):
-            chunk = self.reassembly[pos]
+            chunk = _soundness_check(self.reassembly[pos], DataChunk)
             if start_pos is None:
                 ordered = not (chunk.flags & SCTP_DATA_UNORDERED)
alectryon — _by_sourcemap.py
--- base/_by_sourcemap.py
+++ head/_by_sourcemap.py
@@ -62,5 +62,5 @@
     "/tmp/tmpuegk30i1/alectryon/out/alectryon/json.py": {"by": "sha256:cd372b055df309193be2d6b35d0da9214d610107d53c852c30f56c0cdc70fd44", "py": "sha256:d7fb71c3e96f7ad988eb6beb467d1be8e90676aebd800dcb05456a9dd85aaddf"},
     "/tmp/tmpuegk30i1/alectryon/out/alectryon/latex.py": {"by": "sha256:6cdd7734995b2eb64d93502f494cd37c8b68f6d8655902611cda6b7ef13b12c3", "py": "sha256:29eefc86acfde7b4615df9db04c88690a211dd315d7e5b73f53ec12ec39f0f97"},
-    "/tmp/tmpuegk30i1/alectryon/out/alectryon/lean3.py": {"by": "sha256:aa435debc4a2e4404fc3f4a2a64bef228303b05cec5d51f397581d4c12bbd393", "py": "sha256:dc089614eb94ee3e084ba9821abff5e8900a6b730ed60a957f64bafa090b7734"},
+    "/tmp/tmpuegk30i1/alectryon/out/alectryon/lean3.py": {"by": "sha256:aa435debc4a2e4404fc3f4a2a64bef228303b05cec5d51f397581d4c12bbd393", "py": "sha256:44f138b058d566cd1976e3e1090b5c994d2232390e54c89af846d89dc9312993"},
     "/tmp/tmpuegk30i1/alectryon/out/alectryon/lean4.py": {"by": "sha256:c9002b7cec9d936b92c6022df2480ba48f3f0412a6e0d318e8d3019ec4bbfb14", "py": "sha256:1920659fcdb796d3beeae63d2ad967dca78f30c00e0cb98ec9d4f40bfa8eed39"},
     "/tmp/tmpuegk30i1/alectryon/out/alectryon/literate.py": {"by": "sha256:962c972177b63286cc1356954f8e1211ea29e158fab33fad1a31ecbec7c387de", "py": "sha256:40c137194977c3f4dcfc8ea87a1dd9ec39a475dec41a5aa0987f7bc0fe21b18a"},
alectryon — alectryon/lean3.py
--- base/alectryon/lean3.py
+++ head/alectryon/lean3.py
@@ -137,11 +137,11 @@
 
     def _get_descendants(self, idx: int, parent: int) -> Iterable[Tuple[int, int]]:
-        node: AstNode = _soundness_check(self.ast[idx], dict)
+        node: AstNode = self.ast[idx]
         if node:
             yield idx, parent
-            if _soundness_check(node["kind"], str) in self.TACTIC_CONTAINERS:
+            if node["kind"] in self.TACTIC_CONTAINERS:
                 parent = idx
-            if _soundness_check(node["kind"], str) not in self.DONT_RECURSE_IN:
-                for cidx in _soundness_iter(node.get("children", []), int):
+            if node["kind"] not in self.DONT_RECURSE_IN:
+                for cidx in node.get("children", []):
                     yield from self._get_descendants(cidx, parent)
 
@@ -150,5 +150,5 @@
     def _find_nodes_by_kind(self, *kinds):
         for idx, n in enumerate(self.ast):
-            if n and _soundness_check(n["kind"], str) in kinds:
+            if n and n["kind"] in kinds:
                 yield idx
 
@@ -162,10 +162,10 @@
... 1848 characters elided ...
+        commands = (self.ast[cidx] for r in _soundness_iter(roots, int) for cidx in self.ast[r].get("children", []))
+        cutoffs = [self.document.lc2offset(*c["start"]) for c in commands if "start" in c]
         return self.document.split_fragments(fragments, cutoffs)
antidote — _by_sourcemap.py
--- base/_by_sourcemap.py
+++ head/_by_sourcemap.py
@@ -100,18 +100,18 @@
     "/tmp/tmppmpt6tpp/antidote/out/antidote/core/_wiring.py": {"by": "sha256:dc0ab47eb15d6a0c11d419d8371c9dbb0d74b35131325ed9de9de694829b9c76", "py": "sha256:53d8c37d118ad02d06d24297c0847b6477901ad9dc1e4dff05dd11409420885a"},
     "/tmp/tmppmpt6tpp/antidote/out/antidote/core/annotation.py": {"by": "sha256:8cbb0e7581c783ed034c05f7d685a8e33cb23bb21a6c6b1a8a1346f82f71162e", "py": "sha256:ed297f5f9917396cc6030d96e6f35e1095167a8218acab0063f55e3b8441ddaf"},
-    "/tmp/tmppmpt6tpp/antidote/out/antidote/core/data.py": {"by": "sha256:99b644eba9b56d4de6f8dd3e1245f94e0c2482309e7c6c947ac22466fbd2d68d", "py": "sha256:bf4993217ae20f4e46a590fcef250a2e8450e903deb5a8615dbac9ddaa240ab3"},
+    "/tmp/tmppmpt6tpp/antidote/out/antidote/core/data.py": {"by": "sha256:d911f073a67b1412a96326fc3a8b0b0fec7ea6c40933ef02354819849411f64d", "py": "sha256:bd5dd01772a38c4a68924f20d29572c22087ea38edc22a309b9b3818930f5da2"},
... 4495 characters elided ...
     "/tmp/tmppmpt6tpp/antidote/out/antidote/lib/lazy_ext/__init__.py": {"by": "sha256:71d8b14eb6dd615d43fb5393c83140e87fb2737024f379c77cec62e1eabb7f1b", "py": "sha256:2f3c2cd1a5bf6565154475b9afeeac7c6fc6758385bad20a0e6ebff8773a8791"},
antidote — antidote/core/data.py
--- base/antidote/core/data.py
+++ head/antidote/core/data.py
@@ -1,4 +1,4 @@
 lazy from abc import abstractmethod
-lazy from typing import Callable, final
+lazy from typing import Any, Callable, final
 _MISSING = object()
 def _soundness_check(_v, _t):
@@ -16,5 +16,5 @@
 lazy from dataclasses import dataclass
 lazy from enum import Enum
-from typing import Any, cast, Optional, Sequence, TYPE_CHECKING, TypeVar
+from typing import cast, Optional, Sequence, TYPE_CHECKING, TypeVar
 
 lazy from typing_extensions import final, get_args, get_origin, Protocol, runtime_checkable
antidote — antidote/core/wiring.py
--- base/antidote/core/wiring.py
+++ head/antidote/core/wiring.py
@@ -220,5 +220,5 @@
             if self.ignore_type_hints:
                 raise ValueError("class_in_locals cannot be True if ignoring type hints!")
-            type_hints_locals = dict[str, object](type_hints_locals or {})
+            type_hints_locals = dict(type_hints_locals or {})
             type_hints_locals.setdefault(klass.__name__, klass)
antidote — antidote/lib/interface_ext/_internal.py
--- base/antidote/lib/interface_ext/_internal.py
+++ head/antidote/lib/interface_ext/_internal.py
@@ -191,5 +191,5 @@
     # Extract associated predicate from the type hints
     return [
-        Constraint(predicate_type=_soundness_check(extract_predicate_type(constraint), type), callback=constraint)
+        Constraint(predicate_type=extract_predicate_type(constraint), callback=constraint)
         for constraint in itertools.chain.from_iterable(constraints_groups.values())
     ]
antidote — antidote/lib/interface_ext/_provider.py
--- base/antidote/lib/interface_ext/_provider.py
+++ head/antidote/lib/interface_ext/_provider.py
@@ -185,5 +185,5 @@
     catalog: ProviderCatalog
     lock: threading.RLock
-    candidates_ordered_asc: tuple[CandidateImplementation[Any]]
+    candidates_ordered_asc: tuple[CandidateImplementation[dynamic]]
     default_implementation: Implementation | None
 
@@ -192,5 +192,5 @@
         *,
         catalog: ProviderCatalog,
-        candidates_ordered_asc: tuple[CandidateImplementation[Any]] = _MISSING,
+        candidates_ordered_asc: tuple[CandidateImplementation[dynamic]] = _MISSING,
         default_implementation: Implementation | None = None,
         lock: threading.RLock | None = None,
anyio — _by_sourcemap.py
--- base/_by_sourcemap.py
+++ head/_by_sourcemap.py
@@ -130,5 +130,5 @@
     "/tmp/tmpx7yx064n/anyio/out/tests/test_eventloop.py": {"by": "sha256:aaae8e62097ce72602c5cada0f5a8369e71ad53332d907f0f4ada68becd6badb", "py": "sha256:487101928b879defc5906d4e314aace456e9b620eb9996c0438fca030f980119"},
     "/tmp/tmpx7yx064n/anyio/out/tests/test_fileio.py": {"by": "sha256:e080574af2c619cfd25b44013aaee0ddeafede748630491d819211cfc8f66c7f", "py": "sha256:65234f605e5e5d6c3cd145705c45fdd3c3d42f2014df94cad0e632e1860649bd"},
-    "/tmp/tmpx7yx064n/anyio/out/tests/test_from_thread.py": {"by": "sha256:41e3ab9364383d30da945c13fc92b881630d955782ffd312f365dd4e98ea867b", "py": "sha256:3004348a6ae16811cd95de158cdb75815e1b2e66abf5ed57d09584456b33f3bd"},
+    "/tmp/tmpx7yx064n/anyio/out/tests/test_from_thread.py": {"by": "sha256:41e3ab9364383d30da945c13fc92b881630d955782ffd312f365dd4e98ea867b", "py": "sha256:724560a41b96471eed768ff17d028da74c5bcd561b52c1f5ae9a3a80d42f2c47"},
     "/tmp/tmpx7yx064n/anyio/out/tests/test_functools.py": {"by": "sha256:e189f0a892ac1f2584351208c41cf7f0c1df095c5a849f4655286dd161681958", "py": "sha256:18d72005cd00e8f79e0f7cf925d02ec32ff242db1e9d03eaeeada08d68bebb39"},
     "/tmp/tmpx7yx064n/anyio/out/tests/test_futures.py": {"by": "sha256:af1b33fe440c7fc9ab8735b012ebf96ad799a2587b51e9c36d5f669dd2437b32", "py": "sha256:25b8bb76c888b6ea9c5b2d292cadba68af73e2a136abfad364706ba5a0fe111d"},
anyio — tests/test_from_thread.py
--- base/tests/test_from_thread.py
+++ head/tests/test_from_thread.py
@@ -571,6 +571,6 @@
 
         assert len(results) == 2
-        assert isinstance(results[0], CancelledError)
-        assert isinstance(results[1], CancelledError)
+        assert isinstance(_soundness_check(results[0], (BaseException, type(None))), CancelledError)
+        assert isinstance(_soundness_check(results[1], (BaseException, type(None))), CancelledError)
 
     async def test_aexit_without_exception(self) -> None:
artigraph — _by_sourcemap.py
--- base/_by_sourcemap.py
+++ head/_by_sourcemap.py
@@ -75,5 +75,5 @@
     "/tmp/tmpni9tmux_/artigraph/out/tests/arti/types/test_pyarrow_adapters.py": ("/tmp/tmpni9tmux_/artigraph/tests/arti/types/test_pyarrow_adapters.by", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93]),
... 7384 characters elided ...
     "/tmp/tmpni9tmux_/artigraph/out/tests/arti/versions/test_version.py": {"by": "sha256:ea498ec4c1251aa3e407fdb11fc04fd89b8def08e345bb1bb606e83332ba202d", "py": "sha256:86fa4c68434ef64e4465a21cf13e36ac099341419721396daa63ae2fd69a051c"},
artigraph — arti/backends/memory.py
--- base/arti/backends/memory.py
+++ head/arti/backends/memory.py
@@ -92,5 +92,5 @@
                 snapshot
                 for snapshot in _soundness_iter(partition_snapshots, StoragePartitionSnapshot)
-                if _soundness_check(input_fingerprints.get(snapshot.partition_key), (Fingerprint, type(None))) == snapshot.input_fingerprint
+                if input_fingerprints.get(snapshot.partition_key) == snapshot.input_fingerprint
             }
         return tuple(partition_snapshots)
artigraph — tests/arti/types/test_python_adapters.py
--- base/tests/arti/types/test_python_adapters.py
+++ head/tests/arti/types/test_python_adapters.py
@@ -7,4 +7,8 @@
         )
     return _v
+
+def _soundness_iter(_it, _t):
+    for _x in _it:
+        yield _soundness_check(_x, _t)
 
 lazy import re
@@ -46,9 +50,9 @@
 def test_python_numerics() -> None:
     assert isinstance(python_type_system.to_artigraph(int, hints={}), Int64)
-    for int_type in (Int64, Int32, Int16, Int8):
+    for int_type in _soundness_iter((Int64, Int32, Int16, Int8), type):
         assert python_type_system.to_system(int_type(), hints={}) is int
 
     assert isinstance(python_type_system.to_artigraph(float, hints={}), Float64)
-    for float_type in (Float64, Float32, Float16):
+    for float_type in _soundness_iter((Float64, Float32, Float16), type):
         assert python_type_system.to_system(float_type(), hints={}) is float
asynq — _by_sourcemap.py
--- base/_by_sourcemap.py
+++ head/_by_sourcemap.py
@@ -129,5 +129,5 @@
     "/tmp/tmppumdngd5/asynq/out/asynq/tests/typing_example/param_spec.py": {"by": "sha256:9ef2a751beff808d416584eeefa038c4edbd08be46393e7b086bb71e9ae05248", "py": "sha256:09722a34bdb03a52e081c3ee0673d6d203169a3219213aaeb1403187e41cc6a3"},
     "/tmp/tmppumdngd5/asynq/out/asynq/tests/typing_example/return_type.py": {"by": "sha256:377da89c7624effa329747864c422e36a8a7e11da28a06385ca6b9a96d13ca5c", "py": "sha256:ef40cef9a34667da72a02f77c76e2301b31422d8b6f79f336aefe04c2753bded"},
-    "/tmp/tmppumdngd5/asynq/out/asynq/tools.py": {"by": "sha256:35b5e15c2ce20e7d1489384faf1f4a2c10158730ea0968da7279a722f5685862", "py": "sha256:a10d56c91ef81929e5df2a85e28d554d1cd69473675252e49412c5a08d2296d4"},
+    "/tmp/tmppumdngd5/asynq/out/asynq/tools.py": {"by": "sha256:35b5e15c2ce20e7d1489384faf1f4a2c10158730ea0968da7279a722f5685862", "py": "sha256:1544fe953d8307b38c4ed456355b203a8c733830f08406ce3fa690a995e43ed8"},
     "/tmp/tmppumdngd5/asynq/out/asynq/tools.pyi": {"by": "sha256:141be2387ba7ce569098a03c5aa43ff8866b96cacbb67e67c26a0a17e43ef9a6", "py": "sha256:641a59bfecd592a373aa032376d8b775b1bd3f1deb5200a29c375d40a6af0e75"},
     "/tmp/tmppumdngd5/asynq/out/asynq/utils.py": {"by": "sha256:3badf165023ebbd215d55ac7e7f1302731d61f168a52808b5533a6f29cbc14ff", "py": "sha256:603048c29abd3520e2bd155988754d22d5974fd76e59d60e28355b058571b945"},
asynq — asynq/tools.py
--- base/asynq/tools.py
+++ head/asynq/tools.py
@@ -132,5 +132,5 @@
 
     keys = yield amap.asynq(key_fn, iterable)
-    max_pair = _soundness_check(max(enumerate(iterable), key=lambda pair: keys[pair[0]]), tuple)
+    max_pair = _soundness_check(max(enumerate(iterable), key=lambda pair: keys[_soundness_check(pair[0], int)]), tuple)
     return max_pair[1]
 
@@ -158,5 +158,5 @@
 
     keys = yield amap.asynq(key_fn, iterable)
-    max_pair = _soundness_check(min(enumerate(iterable), key=lambda pair: keys[pair[0]]), tuple)
+    max_pair = _soundness_check(min(enumerate(iterable), key=lambda pair: keys[_soundness_check(pair[0], int)]), tuple)
     return max_pair[1]
attrs — _by_sourcemap.py
--- base/_by_sourcemap.py
+++ head/_by_sourcemap.py
@@ -94,5 +94,5 @@
     "/tmp/tmppmpt6tpp/attrs/out/attr/setters.py": {"by": "sha256:68e7b81bd42d8a3a3d58ebb83c981e128d0555cb830ab08789c8afef0bd8b2ba", "py": "sha256:cd9e88ed1c09b516cd89d87098526fc0dded554e145d910c6bf487f0f6e14d18"},
     "/tmp/tmppmpt6tpp/attrs/out/attr/setters.pyi": {"by": "sha256:248cbc3955cd19780dc44206d735891377b5063fccb2030c7f046b8d76a872ea", "py": "sha256:fe269103d826bc03cce5c2e3757bace1829b2501ca6a16122038b97947fc7a3c"},
-    "/tmp/tmppmpt6tpp/attrs/out/attr/validators.py": {"by": "sha256:033814b2d808b8de44d35e812e50b9e9ccb7b3277f8dab30c365eaf04aaee089", "py": "sha256:e64e25a78c50dcae492f17f7ccf61cd244c38ab1849101cd7a78363370f1fb0e"},
+    "/tmp/tmppmpt6tpp/attrs/out/attr/validators.py": {"by": "sha256:033814b2d808b8de44d35e812e50b9e9ccb7b3277f8dab30c365eaf04aaee089", "py": "sha256:129321174767f4136d39e575b270f4acabea1c6ff8f456c6101dfbcd9b5ae2ee"},
... 1562 characters elided ...
     "/tmp/tmppmpt6tpp/attrs/out/tests/test_packaging.py": {"by": "sha256:a0fc8a3929e2debbf99191434d2ae092d11fa2c2e096077d31daa26810fe2b7f", "py": "sha256:95d1d4db66e939742b5e314ac299aad4143cbfc5f8763b65c54b87fd56da569f"},
attrs — attr/validators.py
--- base/attr/validators.py
+++ head/attr/validators.py
@@ -185,5 +185,5 @@
         msg = "'func' must be one of {}.".format(
             ", ".join(
-                sorted((e and e.__name__) or "None" for e in set(valid_funcs))
+                _soundness_check(sorted((e and e.__name__) or "None" for e in set(valid_funcs)), list)
             )
         )
attrs — tests/test_make.py
--- base/tests/test_make.py
+++ head/tests/test_make.py
@@ -1282,10 +1282,10 @@
             no = attr.field(kw_only=False)
 
-        for cls in [OldClassNewBehavior, NewClassNewBehavior]:
+        for cls in _soundness_iter([OldClassNewBehavior, NewClassNewBehavior], type):
             fs = fields_dict(cls)
             assert fs["yes"].kw_only is True
             assert fs["no"].kw_only is False
 
-        for cls in [OldClassOldBehavior, NewClassOldBehavior]:
+        for cls in _soundness_iter([OldClassOldBehavior, NewClassOldBehavior], type):
             fs = fields_dict(cls)
bandersnatch — _by_sourcemap.py
--- base/_by_sourcemap.py
+++ head/_by_sourcemap.py
@@ -56,5 +56,5 @@
     "/tmp/tmpx7yx064n/bandersnatch/out/bandersnatch_storage_plugins/__init__.py": ("/tmp/tmpx7yx064n/bandersnatch/src/bandersnatch_storage_plugins/__init__.by", []),
... 12751 characters elided ...
     "/tmp/tmpx7yx064n/bandersnatch/out/test_docker_runner.py": {"by": "sha256:4e7ce038a0c3862b91da38f24c0f930e0a6791b47e629d6648076fa21d746a10", "py": "sha256:d2e330ef46fd3fa965c9dc04b30ad1e3f75ba6f5880fe6edb1057ee376347485"},
bandersnatch — bandersnatch_storage_plugins/s3.py
--- base/bandersnatch_storage_plugins/s3.py
+++ head/bandersnatch_storage_plugins/s3.py
@@ -818,7 +818,7 @@
 
         if not hasattr(self, "_verify_semaphore") or self._verify_semaphore is None:
-            concurrency = self.configuration.getint(
+            concurrency = _soundness_check(self.configuration.getint(
                 "s3", "verify_concurrency", fallback=50
-            )
+            ), int)
             self._verify_semaphore: asyncio.Semaphore = asyncio.Semaphore(concurrency)
beartype — _by_sourcemap.py
--- base/_by_sourcemap.py
+++ head/_by_sourcemap.py
@@ -217,5 +217,5 @@
... 16880 characters elided ...
     "/tmp/tmpn5dgw6if/beartype/out/beartype_test/a00_unit/a20_util/hint/a00_pep/__init__.py": {"by": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "py": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},
beartype — beartype/_decor/_nontype/decornontype.py
--- base/beartype/_decor/_nontype/decornontype.py
+++ head/beartype/_decor/_nontype/decornontype.py
@@ -19,4 +19,8 @@
         )
     return _v
+
+def _soundness_iter(_it, _t):
+    for _x in _it:
+        yield _soundness_check(_x, _t)
 
 
@@ -193,5 +197,5 @@
 
     # For each superclass of this object...
-    for obj_base in obj_bases:
+    for obj_base in _soundness_iter(obj_bases, type):
         #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
         # CAUTION: Synchronize with the "PHASE 1" heuristic implemented above.
beartype — beartype/_util/kind/sequence/utilseqmake.py
--- base/beartype/_util/kind/sequence/utilseqmake.py
+++ head/beartype/_util/kind/sequence/utilseqmake.py
@@ -8,4 +8,12 @@
 returning various kinds of sequences, both mutable and immutable).
 '''
+def _soundness_check(_v, _t):
+    if not isinstance(_v, _t):
+        raise TypeError(
+            f"type soundness violation: expected {getattr(_t, '__name__', _t)}, "
+            f"got {type(_v).__name__}"
+        )
+    return _v
+
 
 # ....................{ IMPORTS                            }....................
@@ -41,5 +49,5 @@
         # reversing an existing list into a newly reversed list. See also:
         #     https://stackoverflow.com/a/3705705/2809027
-        stack = sequence[::-1]  # behold! the Martian smiley face emoji! ::-1
+        stack = _soundness_check(sequence[::-1], list)  # behold! the Martian smiley face emoji! ::-1
     # Else, this sequence is *NOT* also a list. In this case...
     else:
beartype — beartype_test/a00_unit/a20_util/func/test_utilfuncwrap.py
--- base/beartype_test/a00_unit/a20_util/func/test_utilfuncwrap.py
+++ head/beartype_test/a00_unit/a20_util/func/test_utilfuncwrap.py
@@ -229,5 +229,5 @@
 '''
 
-        so_much_of_life_and_joy_is_lost = staticmethod(becomes_its_spoil)
+        so_much_of_life_and_joy_is_lost: staticmethod[(), str] = staticmethod(becomes_its_spoil)
 
     # ....................{ PASS                           }....................
@@ -345,5 +345,5 @@
 '''
 
-        so_much_of_life_and_joy_is_lost = staticmethod(becomes_its_spoil)
+        so_much_of_life_and_joy_is_lost: staticmethod[(), str] = staticmethod(becomes_its_spoil)
 
     # ....................{ PASS                           }....................
black — _by_sourcemap.py
--- base/_by_sourcemap.py
+++ head/_by_sourcemap.py
@@ -26,5 +26,5 @@
... 21397 characters elided ...
     "/tmp/tmpni9tmux_/black/out/tests/test_ipynb.py": {"by": "sha256:12db4a150b05f7ad50c025ac3bed459eeec358d855c6cd3f9d215ed8ea48232f", "py": "sha256:a90a09f7d437bfe86ddf130dfa811eb30d2b8c88450a552bdfc1ad7b2d6f44b0"},
black — black/comments.py
--- base/black/comments.py
+++ head/black/comments.py
@@ -91,7 +91,7 @@
     """
     total_consumed = 0
-    for pc in _soundness_iter(list_comments(
+    for pc in list_comments(
         leaf.prefix, is_endmarker=leaf.type == token.ENDMARKER, mode=mode
-    ), ProtoComment):
+    ):
         total_consumed = pc.consumed
         prefix = make_simple_prefix(pc.newlines, pc.form_feed)
@@ -281,5 +281,5 @@
     fmt_off_idx = None
     fmt_on_idx = None
-    for idx, c in enumerate[ProtoComment](all_comments):
+    for idx, c in enumerate(all_comments):
         if fmt_off_idx is None and contains_fmt_directive(c.value, FMT_OFF):
             fmt_off_idx = idx
@@ -296,6 +296,6 @@
         return False
 
-    comment = _soundness_check(all_comments[fmt_off_idx], ProtoComment)
-    fmt_on_comment = _soundness_check(all_comments[fmt_on_idx], ProtoComment)
+    comment = all_comments[fmt_off_idx]
+    fmt_on_comment = all_comments[fmt_on_idx]
     original_prefix = leaf.prefix
 
@@ -317,5 +317,5 @@
         # Use the consumed position of the last comment before fmt:off
... 705 characters elided ...
     """
     fmt_on = False
-    for comment in _soundness_iter(list_comments(container.prefix, is_endmarker=False, mode=mode), ProtoComment):
+    for comment in list_comments(container.prefix, is_endmarker=False, mode=mode):
         if contains_fmt_directive(comment.value, FMT_ON):
             fmt_on = True
black — black/debug.py
--- base/black/debug.py
+++ head/black/debug.py
@@ -63,6 +63,6 @@
         Convenience method for debugging.
         """
-        v: DebugVisitor[None] = DebugVisitor()
+        v: DebugVisitor[None] = DebugVisitor[None]()
         if isinstance(code, str):
             code = lib2to3_parse(code)
-        list(v.visit(code))
+        list[None](v.visit(code))
black — black/linegen.py
--- base/black/linegen.py
+++ head/black/linegen.py
@@ -148,5 +148,5 @@
 
         if len(self.current_line.leaves) == 1 and is_async_stmt_or_funcdef(
-            self.current_line.leaves[0]
+            _soundness_check(self.current_line.leaves[0], Leaf)
         ):
             # Special case for async def/for/with statements. `visit_async_stmt`
@@ -444,6 +444,6 @@
                 leaf.fmt_pass_converted_first_leaf is None
                 and len(self.current_line.leaves) == 1
-                and self.current_line.leaves[0].type == token.LPAR
-                and not self.current_line.leaves[0].value
+                and _soundness_check(self.current_line.leaves[0], Leaf).type == token.LPAR
+                and not _soundness_check(self.current_line.leaves[0], Leaf).value
             )
             # This is a fmt:off/on block from normalize_fmt_off - we still need
black — black/ranges.py
--- base/black/ranges.py
+++ head/black/ranges.py
@@ -206,5 +206,5 @@
     replacements = _NodeReplacements()
     visitor = _TopLevelStatementsVisitor(lines_set, replacements)
-    _ = list(visitor.visit(src_node))  # Consume all results.
+    _ = list[None](visitor.visit(src_node))  # Consume all results.
     replacements.apply()
     _convert_unchanged_line_by_line(src_node, lines_set)
black — black/strings.py
--- base/black/strings.py
+++ head/black/strings.py
@@ -388,5 +388,5 @@
     body = s[first_quote_pos + len(orig_quote) : -len(orig_quote)]
     if "r" in prefix.casefold():
-        if _soundness_check(unescaped_new_quote.search(body), (Match, type(None))):
+        if unescaped_new_quote.search(body):
             # There's at least one unescaped new_quote in this raw string
             # so converting is impossible
@@ -458,5 +458,5 @@
     if is_raw_fstring:
         for middle in _soundness_iter(middles, Leaf):
-            if _soundness_check(unescaped_new_quote.search(middle.value), (Match, type(None))):
+            if unescaped_new_quote.search(middle.value):
                 # There's at least one unescaped new_quote in this raw string
                 # so converting is impossible
black — black/trans.py
--- base/black/trans.py
+++ head/black/trans.py
@@ -1826,5 +1826,5 @@
             if use_custom_breakpoints:
                 # Custom User Split (manual)
-                csplit = custom_splits.pop(0)
+                csplit = _soundness_check(custom_splits.pop(0), CustomSplit)
                 break_idx = csplit.break_idx
             else:
black — tests/test_docs.py
--- base/tests/test_docs.py
+++ head/tests/test_docs.py
@@ -77,5 +77,5 @@
     preview_error = check_feature_list(
         future_style,
-        {feature.name for feature in set(Preview) - UNSTABLE_FEATURES},
+        {feature.name for feature in _soundness_iter(set[Preview](Preview) - UNSTABLE_FEATURES, Preview)},
         "preview",
     )
bokeh — _by_sourcemap.py
--- base/_by_sourcemap.py
+++ head/_by_sourcemap.py
@@ -461,5 +461,5 @@
     "/tmp/tmpuegk30i1/bokeh/out/examples/server/app/spectrogram/audio.py": ("/tmp/tmpuegk30i1/bokeh/examples/server/app/spectrogram/audio.by", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68]),
... 123176 characters elided ...
     "/tmp/tmpuegk30i1/bokeh/out/tests/unit/bokeh/test_server.py": {"by": "sha256:03ceb5e7851b778c2e34e55c6d7b379f727a2f1fe9a803a4bcef78a9e7beb002", "py": "sha256:619b8999c9b9485a4c9b4a879aadb699729a1c104d113822027d4b3d72b20d89"},
bokeh — bokeh/core/property/dataspec.py
--- base/bokeh/core/property/dataspec.py
+++ head/bokeh/core/property/dataspec.py
@@ -317,5 +317,5 @@
 class NullStringSpec(DataSpec):
     def __init__(self, default: Any = None, *, help: str | None = None) -> None:
-        super().__init__(Nullable(String), default=default, help=help)
+        super().__init__(Nullable[str](String), default=default, help=help)
 
 class StringSpec(DataSpec):
@@ -413,5 +413,5 @@
 
     def __init__(self, default: Any, *, help: str | None = None) -> None:
-        super().__init__(Nullable(HatchPatternType), default=default, help=help)
+        super().__init__(Nullable[str](HatchPatternType), default=default, help=help)
 
 class MarkerSpec(DataSpec):
@@ -541,5 +541,5 @@
     def __init__(self, default: Any = None, units_default: Any = "data", *, help: str | None = None) -> None:
         super().__init__(default=default, units_default=units_default, help=help)
-        self.value_type = Nullable(Float)
+        self.value_type = Nullable[int | float](Float)
         self._type_params = [Null(), *self._type_params]
bokeh — bokeh/core/property/visual.py
--- base/bokeh/core/property/visual.py
+++ head/bokeh/core/property/visual.py
@@ -242,6 +242,6 @@
 
             Tuple(Float, Float),
-            Tuple(Nullable(Float), Float),
-            Tuple(Float, Nullable(Float)),
+            Tuple(Nullable[int | float](Float), Float),
+            Tuple(Float, Nullable[int | float](Float)),
 
             Tuple(TimeDelta, TimeDelta),
bokeh — bokeh/core/property_aliases.py
--- base/bokeh/core/property_aliases.py
+++ head/bokeh/core/property_aliases.py
@@ -71,5 +71,5 @@
 
 type PixelsType = int
-Pixels = NonNegative(Int)
+Pixels = NonNegative[int](Int)
 
 type HAnchorType = enums.AlignType | enums.HAlignType | PercentType
@@ -121,8 +121,8 @@
         Tuple(Pixels, Pixels, Pixels, Pixels),
         Struct(
-            top_left=Optional(Pixels),
-            top_right=Optional(Pixels),
-            bottom_right=Optional(Pixels),
-            bottom_left=Optional(Pixels),
+            top_left=Optional[int](Pixels),
+            top_right=Optional[int](Pixels),
+            bottom_right=Optional[int](Pixels),
+            bottom_left=Optional[int](Pixels),
         ),
     )
@@ -137,13 +137,13 @@
         Tuple(Pixels, Pixels),
         Struct(
-            x=Optional(Pixels),
-            y=Optional(Pixels),
+            x=Optional[int](Pixels),
+            y=Optional[int](Pixels),
         ),
         Tuple(Pixels, Pixels, Pixels, Pixels),
         Struct(
-            left=Optional(Pixels),
-            right=Optional(Pixels),
-            top=Optional(Pixels),
-            bottom=Optional(Pixels),
+            left=Optional[int](Pixels),
+            right=Optional[int](Pixels),
+            top=Optional[int](Pixels),
+            bottom=Optional[int](Pixels),
         ),
     )
bokeh — bokeh/core/property_mixins.py
--- base/bokeh/core/property_mixins.py
+++ head/bokeh/core/property_mixins.py
@@ -289,5 +289,5 @@
     hatch_alpha: Alpha = Alpha(help=_alpha_help % "hatching")
     hatch_scale: Size = Size(default=12.0, help=_hatch_scale_help)
-    hatch_pattern = Nullable(String, help=_hatch_pattern_help)  # String to accommodate user custom values
+    hatch_pattern: Nullable[str] = Nullable[str](String, help=_hatch_pattern_help)  # String to accommodate user custom values
     hatch_weight: Size = Size(default=1.0, help=_hatch_weight_help)
     hatch_extra = Dict(String, Instance("bokeh.models.textures.Texture"))
bokeh — bokeh/model/model.py
--- base/bokeh/model/model.py
+++ head/bokeh/model/model.py
@@ -161,5 +161,5 @@
         return self._id
 
-    name = Nullable(String, help="""\
+    name: Nullable[str] = Nullable[str](String, help="""\
 An arbitrary, user-supplied name for this model.
 
@@ -230,5 +230,5 @@
 """)
 
-    subscribed_events = Set(String, help="""\
+    subscribed_events: Set[str] = Set[str](String, help="""\
 Collection of events that are subscribed to by Python callbacks. This is
 the set of events that will be communicated from BokehJS back to Python
bokeh — bokeh/models/annotations/dimensional.py
--- base/bokeh/models/annotations/dimensional.py
+++ head/bokeh/models/annotations/dimensional.py
@@ -74,13 +74,13 @@
         super().__init__(**kwargs)
 
-    ticks = Required(List(Float), help="""\
+    ticks: Required[list[int | float]] = Required[list[int | float]](List[int | float](Float), help="""\
 Preferred values to choose from in non-exact mode.\
 """)
 
-    include = Nullable(List(String), default=None, help="""\
+    include: Nullable[list[str]] = Nullable[list[str]](List[str](String), default=None, help="""\
 An optional subset of preferred units from the basis.\
 """)
 
-    exclude = List(String, default=[], help="""\
+    exclude: List[str] = List[str](String, default=[], help="""\
 A subset of units from the basis to avoid.\
 """)
@@ -125,9 +125,9 @@
         super().__init__(**kwargs)
 
-    base_unit = Required(String, help="""\
+    base_unit: Required[str] = Required[str](String, help="""\
 The short name of the base unit, e.g. ``"m"`` for meters or ``"eV"`` for electron volts.\
 """)
 
-    full_unit = Nullable(String, default=None, help="""\
+    full_unit: Nullable[str] = Nullable[str](String, default=None, help="""\
 The full name of the base unit, e.g. ``"meter"`` or ``"electronvolt"``.\
 """)
bokeh — bokeh/models/annotations/geometry.py
--- base/bokeh/models/annotations/geometry.py
+++ head/bokeh/models/annotations/geometry.py
@@ -223,5 +223,5 @@
 """)
 
-    min_width: NonNegative[int] = NonNegative[int](Float, default=0, help="""\
+    min_width: NonNegative[int | float] = NonNegative[int | float](Float, default=0, help="""\
 Allows to set the minimum width of the box.
 
@@ -230,5 +230,5 @@
 """)
 
-    min_height: NonNegative[int] = NonNegative[int](Float, default=0, help="""\
+    min_height: NonNegative[int | float] = NonNegative[int | float](Float, default=0, help="""\
 Allows to set the maximum width of the box.
 
@@ -469,9 +469,9 @@
         super().__init__(*args, **kwargs)
 
-    gradient = Nullable(Float, help="""\
+    gradient: Nullable[int | float] = Nullable[int | float](Float, help="""\
 The gradient of the line, in |data units|\
 """)
 
-    y_intercept = Nullable(Float, help="""\
+    y_intercept: Nullable[int | float] = Nullable[int | float](Float, help="""\
 The y intercept of the line, in |data units|\
 """)
bokeh — bokeh/models/annotations/legends.py
--- base/bokeh/models/annotations/legends.py
+++ head/bokeh/models/annotations/legends.py
@@ -10,4 +10,5 @@
 from typing import Any, TYPE_CHECKING
 if TYPE_CHECKING:
+    from typing import Never
     import types
 
@@ -292,10 +293,10 @@
 """)
 
-    display_low = Nullable(Float, help="""\
+    display_low: Nullable[int | float] = Nullable[int | float](Float, help="""\
 The lowest value to display in the color bar. The whole of the color entry
 containing this value is shown.\
 """)
 
-    display_high = Nullable(Float, help="""\
+    display_high: Nullable[int | float] = Nullable[int | float](Float, help="""\
 The highest value to display in the color bar. The whole of the color entry
 containing this value is shown.\
@@ -328,5 +329,5 @@
 """)
 
-    levels = Seq(Float, default=[], help="""\
+    levels: Seq[int | float, list[Never]] = Seq(Float, default=[], help="""\
 Levels at which the contours are calculated.\
 """)
@@ -354,5 +355,5 @@
 """)
 
-    index = Nullable(Int, help="""\
+    index: Nullable[int] = Nullable[int](Int, help="""\
 The column data index to use for drawing the representative items.
 
@@ -423,5 +424,5 @@
 """)
 
-    title = Nullable(String, help="""\
+    title: Nullable[str] = Nullable[str](String, help="""\
 The title text to render.\
 """)
bokeh — bokeh/models/callbacks.py
--- base/bokeh/models/callbacks.py
+++ head/bokeh/models/callbacks.py
@@ -131,5 +131,5 @@
 """)
 
-    code = Required(String)(help="""\
+    code = Required[str](String)(help="""\
 A snippet of JavaScript code to execute in the browser.
 
@@ -269,5 +269,5 @@
 """)
 
-    attr: str = Required(String, help="""\
+    attr: str = Required[str](String, help="""\
 The property to modify.\
 """)
bokeh — bokeh/models/css.py
--- base/bokeh/models/css.py
+++ head/bokeh/models/css.py
@@ -68,5 +68,5 @@
         super().__init__(*args, **kwargs)
 
-    css = Required(String, help="""\
+    css: Required[str] = Required[str](String, help="""\
 The contents of this stylesheet.\
 """)
@@ -86,5 +86,5 @@
         super().__init__(*args, **kwargs)
 
-    url = Required(String, help="""\
+    url: Required[str] = Required[str](String, help="""\
 The location of an external stylesheet.\
 """)
@@ -121,209 +121,209 @@
         super().__init__(*args, **kwargs)
 
-    align_content = Nullable(String)
-    align_items = Nullable(String)
-    align_self = Nullable(String)
-    alignment_baseline = Nullable(String)
-    all = Nullable(String)
-    animation = Nullable(String)
-    animation_delay = Nullable(String)
-    animation_direction = Nullable(String)
-    animation_duration = Nullable(String)
-    animation_fill_mode = Nullable(String)
-    animation_iteration_count = Nullable(String)
-    animation_name = Nullable(String)
-    animation_play_state = Nullable(String)
-    animation_timing_function = Nullable(String)
... 29921 characters elided ...
+    word_spacing: Nullable[str] = Nullable[str](String)
+    word_wrap: Nullable[str] = Nullable[str](String)
+    writing_mode: Nullable[str] = Nullable[str](String)
+    z_index: Nullable[str] = Nullable[str](String)
 
 #-----------------------------------------------------------------------------
bokeh — bokeh/models/dom.py
--- base/bokeh/models/dom.py
+++ head/bokeh/models/dom.py
@@ -87,5 +87,5 @@
         super().__init__(*args, **kwargs)
 
-    style: Either = Either(Instance(Styles), Dict(String, String), default={})
+    style: Either = Either(Instance(Styles), Dict[str, str](String, String), default={})
 
     children = List(Either(String, Instance(DOMNode), Instance(UIElement)), default=[])
@@ -159,9 +159,9 @@
 """)
 
-    attr = Required(String, help="""\
+    attr: Required[str] = Required[str](String, help="""\
 The name of the property whose value will be observed.\
 """)
 
-    format = Nullable(String, default=None, help="""\
+    format: Nullable[str] = Nullable[str](String, default=None, help="""\
 Optional format string, which is equivalent to using ``"@{field}{format}"``.\
 """)
@@ -205,9 +205,9 @@
         super().__init__(*args, **kwargs)
 
-    field = Required(String, help="""\
+    field: Required[str] = Required[str](String, help="""\
 The name of the field to reference, which is equivalent to using ``"@{field}``.\
 """)
 
-    format = Nullable(String, default=None, help="""\
+    format: Nullable[str] = Nullable[str](String, default=None, help="""\
 Optional format string, which is equivalent to using ``"@{field}{format}"``.\
 """)
bokeh — bokeh/models/filters.py
--- base/bokeh/models/filters.py
+++ head/bokeh/models/filters.py
@@ -169,5 +169,5 @@
     '''
 
-    column_name = Required(String, help="""\
+    column_name: Required[str] = Required[str](String, help="""\
 The name of the column to perform the group filtering operation on.\
 """)

💥 fails to round-trip (unchanged from base)

  • alerta: build: killed: timed out after 900s
  • apprise: build: killed: exceeded 11.7365GB memory budget

669 finding(s) omitted to fit GitHub's 65536-character comment limit. Every finding, with nothing elided, is in the roundtrip-report.md artifact of this run.

@KotlinIsland
KotlinIsland force-pushed the merge-upstream branch 2 times, most recently from 86507e1 to e8f1a60 Compare September 6, 2026 09:46
@KotlinIsland
KotlinIsland merged commit 3cb38f0 into main Sep 6, 2026
89 of 93 checks passed
@KotlinIsland
KotlinIsland deployed to release-playground September 6, 2026 14:22 — with GitHub Actions Active
@KotlinIsland
KotlinIsland deleted the merge-upstream branch September 6, 2026 17:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.